Libraries

Functions


draw_pie_chart <- function(data.df, column) {
  data_counts.df <- data.df %>%
    dplyr::select(id, {{column}}) %>%
    distinct() %>%
    group_by({{column}}) %>%
    summarise(counts = n()) %>%
    arrange(desc(counts)) %>%
    mutate(percentage = percent(counts / sum(counts))) 
  
  pie <- ggplot(data_counts.df, aes(x="", y=counts, fill={{column}})) +
    geom_bar(stat = "identity", width = 0.05, color="black") +
    coord_polar("y", start=0) +
    scale_fill_brewer(palette = "Set3", direction = -4) +
    geom_label_repel(aes(label = percentage), size=3.5, show.legend = F, position = position_stack(vjust = .5)) +
    guides(fill = guide_legend(title = "Stem type")) +
    ggtitle(paste0("Stem types - ", sum(data_counts.df$counts)," duplexes")) +
    theme_void() # remove background, grid, numeric labels
  return(pie)
}


# get_stem_length <- function(data.df) {
#   data_sh.df <- data.df %>%
#     group_by(id) %>%
#     dplyr::filter(element == "s0" | element == "h0") %>%  #use these positions to estimate stem length
#     mutate(stem_length_L = L_start - lag(L_start),
#            stem_length_R = lag(R_end) - L_end) %>%
#     dplyr::filter(element == "h0") %>%
#     dplyr::select(id, stem_length_L, stem_length_R)
#   data.df <- left_join(data.df, data_sh.df, by = "id")
#   return(data.df)
# }


# get_hairpin_length <- function(data.df) {
#   data_h.df <- data.df %>% 
#     group_by(id) %>%
#     dplyr::filter(element == "h0") %>%
#     mutate(hairpin_length = sum(L_width)) %>%
#     dplyr::select(id, hairpin_length)
#   data.df <- left_join(data.df, data_h.df, by = "id")
#   return(data.df)
# }


get_paired_regions <- function(data.df) {
  duplex_max.df <- data.df %>%
    group_by(id) %>%
    dplyr::filter(element_type == "s") %>%
    summarise(max_duplex = max(L_width),
              sum_paired = sum(L_width)) %>%
    dplyr::select(id, max_duplex, sum_paired)
  data.df <- left_join(data.df, duplex_max.df)
  
}


count_element <- function(data.df, structure_type) {
  counter <- data.df %>% 
    group_by(id) %>% 
    dplyr::filter(element_type == structure_type) %>%
    summarise(count=n())
  return(counter)
}


analyse_duplexes <- function(data.df) {
  data.df <- get_paired_regions(data.df)
  # data.df <- get_stem_length(data.df)
  # data.df <- get_hairpin_length(data.df)
  #data.df <- get_iloop_length_sum(data.df)
  
  iloop_counts.df <- count_element(data.df, "i") %>% 
    dplyr::select(id, count) %>%
    dplyr::rename(iloop_counts = count)
  data.df <- left_join(data.df, iloop_counts.df, by = "id")
  return(data.df)
}


get_id_features <- function(data.df) {
  id_features.df <- data.df %>%
    dplyr::select(id, max_duplex, sum_paired, iloop_counts) %>%
    distinct() %>%
    replace(is.na(.), 0)
  return(id_features.df)
}
plot_stacked_barchart <- function(data.df, column, group) {
  
  data_counts.df <- data.df %>%
    dplyr::select(name, {{group}}, {{column}}) %>%
    group_by({{group}}, {{column}}) %>%
    summarise(counts = n()) %>%
    arrange(desc({{column}})) %>%
    mutate(percentage = counts*100 / sum(counts)) %>%
    mutate(pos = cumsum(percentage) - percentage/2)
  
  bar = ggplot() + geom_bar(aes(y = percentage, x = "", fill = {{column}}), alpha = 0.8, data = data_counts.df,
                            stat="identity", width = 0.5) +
    geom_text(data = data_counts.df, aes(x = "", y = pos, label = paste0(round(percentage,1),"%")), size=3) +
    facet_grid(cols = vars({{group}})) +
    #facet_wrap(. ~ Experiment, scales = "free") +
    scale_fill_paletteer_d("rcartocolor::Earth", direction = -1) +
    theme(legend.position="right", legend.direction="vertical",
          legend.title = element_blank()) +
    theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank()) +
    ylab("Percentage") +
    xlab("Experiment")
  return(bar)
  
}
get_nucleotide_frequency <- function(duplexes.df) {
  
  duplexes.df <- duplexes.df %>%
    dplyr::filter(!is.na(l_paired_residues)) %>%
    dplyr::filter(!is.na(r_paired_residues))
  
  L_seq <- DNAStringSet(duplexes.df$l_paired_residues)
  R_seq <- DNAStringSet(duplexes.df$r_paired_residues)
  
  L_freq <- as.data.frame(oligonucleotideFrequency(L_seq, width = 1, as.prob=TRUE))
  R_freq <- as.data.frame(oligonucleotideFrequency(R_seq, width = 1, as.prob=TRUE))
  duplexes.df <- dplyr::bind_cols(duplexes.df, L_freq, R_freq)
  
  colnames(duplexes.df) = gsub("...41|...42|...43|...44", "_L", colnames(duplexes.df)) # rename columns
  colnames(duplexes.df) = gsub("...45|...46|...47|...48", "_R", colnames(duplexes.df))
  
  # Pur content - calculate max pur L vs. R arm 
  duplexes.df <- duplexes.df %>%
    rowwise() %>% 
    mutate(L_pur = sum(c(A_L, G_L)), # purine total per hybrid
           R_pur = sum(c(A_R, G_R))) %>%
    mutate(max_pur = max(L_pur, R_pur)) %>%
    mutate(arm = case_when((max_pur == L_pur) ~ "L", # assign maximal purine arm
                           TRUE ~ "R")) %>%
    ungroup() 
  
  # reorient duplex frequencies based on maximal purine content (column "arm")
  nuc_freq_max_pur_arm.df <- duplexes.df %>%
    dplyr::filter(arm == "L") %>%
    mutate(A_freq_max_pur_arm = A_L, A_freq_min_pur_arm = A_R,
           G_freq_max_pur_arm = G_L, G_freq_min_pur_arm = G_R,
           C_freq_max_pur_arm = C_L, C_freq_min_pur_arm = C_R,
           U_freq_max_pur_arm = T_L, U_freq_min_pur_arm = T_R)

  nuc_freq_min_pur_arm.df <- duplexes.df %>%
    dplyr::filter(arm == "R") %>%
    mutate(A_freq_max_pur_arm = A_R, A_freq_min_pur_arm = A_L,
           G_freq_max_pur_arm = G_R, G_freq_min_pur_arm = G_L,
           C_freq_max_pur_arm = C_R, C_freq_min_pur_arm = C_L,
           U_freq_max_pur_arm = T_R, U_freq_min_pur_arm = T_L)

  nuc_freq_reordered.df <- rbind(nuc_freq_max_pur_arm.df, nuc_freq_min_pur_arm.df)
  nuc_freq_reordered.df <- nuc_freq_reordered.df %>% arrange(desc(max_pur))
  
  return(nuc_freq_reordered.df)
  
}
plot_nucleotide_frequency <- function(data) {
  
  data <- data %>%
    dplyr::select(id, A_freq_max_pur_arm, G_freq_max_pur_arm, C_freq_max_pur_arm, U_freq_max_pur_arm,
                  A_freq_min_pur_arm, G_freq_min_pur_arm, C_freq_min_pur_arm, U_freq_min_pur_arm) %>%
    dplyr::distinct() %>%
    drop_na() %>%
    dplyr::arrange(desc(G_freq_max_pur_arm))
  
  # Prepare for plotting
  high_pur.df <- data %>% 
    dplyr::select(id, A_freq_max_pur_arm, G_freq_max_pur_arm, C_freq_max_pur_arm, U_freq_max_pur_arm)
  high_pur.df <- rowid_to_column(high_pur.df)
  long_high_pur.df <- high_pur.df %>% 
    gather(residue, frequency, A_freq_max_pur_arm:U_freq_max_pur_arm)
  long_high_pur.df$arm = "High purine arm"
  
  low_pur.df <- data %>% 
    dplyr::select(id, A_freq_min_pur_arm, G_freq_min_pur_arm, C_freq_min_pur_arm, U_freq_min_pur_arm)

  low_pur.df <- rowid_to_column(low_pur.df)
  long_low_pur.df <- low_pur.df %>% 
    gather(residue, frequency, A_freq_min_pur_arm:U_freq_min_pur_arm)
  long_low_pur.df$arm = "Low purine arm"
  
  nuc_freq.df <- rbind(long_high_pur.df, long_low_pur.df)
  nuc_freq.df$residue <- str_sub(nuc_freq.df$residue, 1,1) #trim string to get nucleotide (first) letter
  
  # Plot individual nucleotide frequencies
  nuc_freq.gg <- ggplot(nuc_freq.df, aes(x=rowid, y=frequency, color = residue)) +
    geom_smooth(se=F) + coord_flip() +
    facet_grid(cols = vars(arm)) +
    ylab("Frequency") +
    xlab("ID") +
    ylim(0, 1) +
    scale_color_manual(values = c("darkgreen","dodgerblue4","goldenrod3","firebrick"))
  
  return(nuc_freq.gg)
  
}
run_rnaeval <- function(fasta.file, args =  c("-i", paste0(fasta.file,">temp.fa"))) {
  
  rnaeval.out <- system2(command = "RNAeval", args = args, stdout = TRUE)
  rnaeval.df <- as.data.frame(readBStringSet("temp.fa"))
  rnaeval.df <- rnaeval.df %>% rownames_to_column(var = "id")
  
  rnaeval.df <- rnaeval.df %>%
    rowwise() %>%
    mutate(eval_mfe = as.numeric(str_sub(x, -7,-2))) %>%
    ungroup() %>%
    dplyr::select(id, eval_mfe)

  return(rnaeval.df)
  
}


get_mfe <- function(forgi, rnafold, prefix) {
  
  data.df <- forgi %>%
    dplyr::filter(element_type == "s") %>%
    group_by(id) %>%
    mutate(L_limit = min(L_start),
           R_limit = max(R_end)) %>%
    dplyr::select(id, L_limit, R_limit) %>%
    distinct() %>%
    ungroup()
  
  data.df <- left_join(data.df , rnafold, by = "id") # add the sequence and structure to the forgi
  data.df <- data.df %>%
    mutate(stem_loop_seq = substr(sequence, L_limit, R_limit),
           stem_loop_db = substr(mea_structure, L_limit, R_limit)) %>%
    dplyr::select(-sequence, -mea_structure)
  # write multi-fasta with seq and db
  fasta <- character(nrow(data.df) * 3) # empty file
  fasta[c(TRUE, FALSE, FALSE)] <- paste0(">", data.df$id)
  fasta[c(FALSE, TRUE, FALSE)] <- data.df$stem_loop_seq
  fasta[c(FALSE, FALSE, TRUE)] <- data.df$stem_loop_db
  
  writeLines(fasta, paste0(prefix,"_rnaeval.fasta"))
  mfe.df <- run_rnaeval(paste0(prefix,"_rnaeval.fasta"))
  data.df <- left_join(data.df, mfe.df, by = "id")
  forgi <- left_join(forgi, data.df, by = "id")
  
  invisible(file.remove("temp.fa"))
  return(forgi)
  
}

Data

# Outputs from mfe and forgi analyses
setwd("~/Documents/projects/computational_hiCLIP/notebooks")
The working directory was changed to /Users/iosubi/Documents/projects/computational_hiCLIP/notebooks inside a notebook chunk. The working directory will be reset when the chunk is finished running. Use the knitr root.dir option in the setup chunk to change the working directory for notebook chunks.
nolinker.dt <- fread("stau1_3utr_minmfe_cluster_hybrids.mfe_analyses.tsv.gz") # analyse_nolinker_duplexes.R output
nolinker_forgi.dt <- fread("stau1_3utr_minmfe_cluster_hybrids.forgi.tsv.gz") # similar but for PARIS

paris.dt <- fread("paris_3utr_minmfe_cluster_hybrids.mfe_analyses.tsv")
paris.forgi.dt <- fread("paris_3utr_minmfe_cluster_hybrids.forgi.tsv.gz")

nornase.forgi.dt <- fread("stau1.peaks.10nt_10nt_forgi_analyses.df.txt")

rnafold.df <- read.csv("/Users/iosubi/Documents/projects/computational_hiCLIP/nonhybrids_10nt_10nt/stau1.rnafold.tsv.gz", sep = "\t")
rnafold.df <- rowid_to_column(rnafold.df, "id")
rnafold.df$id <- paste0("ID", rnafold.df$id, sep="")
rnafold.df <- dplyr::select(rnafold.df, c(id, sequence, mea_structure))
# Annotations
genes.gr <- rtracklayer::import.gff2("/Users/iosubi/Documents/projects/computational_hiCLIP/files/human_GencodeV33.gtf.gz")
regions.gr <- import.gff2("~/Documents/Genomes/human/regions.gtf.gz")

# Clusters (Tosca)
hybrids.dt <- fread("/Users/iosubi/Documents/projects/computational_hiCLIP/files/all.clusters.tsv.gz")
paris_clusters.dt <- fread("/Users/iosubi/Documents/projects/computational_hiCLIP/files/paris/all.clusters.tsv.gz")
|--------------------------------------------------|
|==================================================|

MFE comparisons

These analyses were performed on representative hybrids produced with the Tosca pipeline, as well as predicted duplexes using STAU1 peaks and RNAfold Only intramolecular 3’UTR duplexes were analysed. For the hybrid data, the hybrid with min. MFE was chosen to represent each cluster.

No RNase duplexes

Sys.setenv(PATH="/Users/iosubi/opt/miniconda3/bin/")
nonhybrids.mfe.df <- get_mfe(nornase.forgi.dt, rnafold.df, prefix="stau1")
nonhybrids.mfe.df <- nonhybrids.mfe.df %>%
  dplyr::select(id, eval_mfe) %>%
  distinct()
  

nornase.forgi.dt <- left_join(nornase.forgi.dt, nonhybrids.mfe.df, by = "id")

nonhybrids.mfe.df <- nonhybrids.mfe.df %>%
  mutate(Sample = "mfe", Experiment = "STAU1\nnonhybrids")

nonhybrids.mfe.df <- dplyr::rename(nonhybrids.mfe.df, MFE = eval_mfe)
  

No linker duplexes

nolinker.mfe.df <- nolinker.dt %>%
  dplyr::select(id, mfe, shuffled_mfe) %>%
  distinct() 

nolinker.mfe.df <- nolinker.mfe.df %>%
  gather("Sample", "MFE", mfe:shuffled_mfe) %>%
  mutate(Experiment = "STAU1")

PARIS duplexes

paris.mfe.df <- paris.dt %>%
  dplyr::select(id, mfe, shuffled_mfe) %>%
  distinct() 

paris.mfe.df <- paris.mfe.df %>%
  gather("Sample", "MFE", mfe:shuffled_mfe) %>%
  mutate(Experiment = "PARIS")

Pull experiments together

stau1.hyb.nonhyb.dt <- bind_rows(nolinker.mfe.df, paris.mfe.df, nonhybrids.mfe.df)

stau1.hyb.nonhyb.dt <- stau1.hyb.nonhyb.dt %>%
  mutate(sample = case_when((Sample == "mfe") ~ "STAU1",
                            (Sample == "shuffled_mfe") ~ "Shuffled control"))

MFE Density plots

stau1.hyb.nonhyb.gg <- ggplot(stau1.hyb.nonhyb.dt, aes(x = MFE, linetype = Sample)) + 
  geom_density(alpha = 0.8) +
  scale_colour_brewer(palette="Dark2") +
  scale_linetype_manual(values=c( "solid", "longdash"))+
  facet_grid(cols = vars(Experiment)) +
  theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank()) +
  ylab("Density") +
  xlab("MFE (kcal/mol)") +
  ggtitle("3'UTR clusters min MFE")

stau1.hyb.nonhyb.gg

# ggsave("3UTR_clusters_nolinker_minMFE.pdf", stau1.hyb.nonhyb.gg, dpi=300, height = 7, width = 8)                               

MFE Violin plots

# all

stau1.hyb.nonhyb.dt <- stau1.hyb.nonhyb.dt %>%
  dplyr::filter(Sample != "Shuffled control")

mfe.violin.gg <- ggplot(stau1.hyb.nonhyb.dt, aes(x = Experiment, y = MFE, fill = Experiment)) +
  geom_violin() +
  geom_boxplot(width=0.1) +
  scale_fill_brewer(palette="Blues") +
  #scale_color_grey() +
  #stat_summary(fun.y=median, geom="point", size=2, color="grey") +
  theme(legend.position="right", legend.direction="vertical",
          legend.title = element_blank()) +
    theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank())+
  ggtitle("3'UTR clusters min MFE")

mfe.violin.gg

MFE of non-hybrids is higher than STAU1 hybrids and PARIS. Need to filter the non-hybrids based on the length of stems (paired residues)

nolinker_features.df <- nolinker.dt %>%
  dplyr::select(id, total_paired) %>%
  ungroup()

max <- quantile(nolinker_features.df$total_paired, 0.90)[[1]]
min <- quantile(nolinker_features.df$total_paired, 0.2)[[1]]

median(nolinker_features.df$total_paired)
[1] 23

Filter the non-hybrids according to the size of paired residues

filtered.nonhyb.forgi.df <- nornase.forgi.dt %>%
  dplyr::filter(between(sum_paired, 8, max) )
filtered.nonhyb.mfe.df <- filtered.nonhyb.forgi.df %>%
  dplyr::select(id, eval_mfe) %>%
  distinct() %>%
  mutate(Sample = "mfe", Experiment = "STAU1 nonhybrids")

filtered.nonhyb.mfe.df <- dplyr::rename(filtered.nonhyb.mfe.df, MFE = eval_mfe)
# Append datasets for all experiments
stau1.hyb.filtered_nonhyb.dt <- bind_rows(nolinker.mfe.df, paris.mfe.df, filtered.nonhyb.mfe.df)
stau1.hyb.filtered_nonhyb.dt <- stau1.hyb.filtered_nonhyb.dt %>%
  mutate(sample = case_when((Sample == "mfe") ~ "STAU1",
                            (Sample == "shuffled_mfe") ~ "Shuffled control"))
stau1.hyb.filtered_nonhyb.gg <- ggplot(stau1.hyb.filtered_nonhyb.dt, aes(x = MFE, linetype = Sample)) + 
  geom_density(alpha = 0.8) +
  scale_colour_brewer(palette="Dark2") +
  scale_linetype_manual(values=c( "solid", "longdash"))+
  facet_grid(cols = vars(Experiment)) +
  theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank()) +
  ylab("Density") +
  xlab("MFE (kcal/mol)") +
  ggtitle("3'UTR clusters min MFE - filtered")

stau1.hyb.filtered_nonhyb.gg

# all
stau1.hyb.filtered_nonhyb.dt <- stau1.hyb.filtered_nonhyb.dt %>%
  dplyr::filter(Sample != "Shuffled control")

mfe.filtered.violin.gg <- ggplot(stau1.hyb.filtered_nonhyb.dt, aes(x = Experiment, y = MFE, fill = Experiment)) +
  geom_violin() +
  geom_boxplot(width=0.1) +
  scale_fill_brewer(palette="Blues") +
  #scale_color_grey() +
  #stat_summary(fun.y=median, geom="point", size=2, color="grey") +
  theme(legend.position="right", legend.direction="vertical",
          legend.title = element_blank()) +
    theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank())+
  ggtitle("3'UTR clusters min MFE")

mfe.filtered.violin.gg
ggsave("filtered_mfe_comparison.pdf", mfe.filtered.violin.gg, dpi = 300)
Saving 7.29 x 4.51 in image

Duplex types

# No linker

nolinker.feat.df <- nolinker_forgi.dt %>% replace(is.na(.), 0) # replqce NA with zero to simplify filtering

nolinker.feat.df <- nolinker.feat.df %>%
  group_by(id) %>%
  mutate(stem_type = case_when(!any(element_type == "i") ~ "Uninterrupted stem",
                               any(element_type == "i") & (!any(element_type == "i" & (L_width > 1 | R_width > 1))) &
                                 (!any(element_type == "i" & L_width != R_width)) ~ "Stem with only symmetrical bulges",
                               (any(element_type == "i")) & (!any(element_type == "i" & (L_width > 1 | R_width > 1))) &
                                 (any(element_type == "i" & L_width != R_width)) ~ "Stem with non-symmetrical bulges",
                               (any(element_type == "i")) &
                                 (any(element_type == "i" & (L_width > 1) & (R_width == 0 | R_width >= 1))) |
                                 (any(element_type == "i" & (L_width = 1) & (R_width > 1))) ~ "Stem with internal loops"))


nolinker_pie.gg <- draw_pie_chart(nolinker.feat.df, stem_type)
ggsave(paste0("nolinker", "_stem_types_pie.pdf"), nolinker_pie.gg, height = 11, width = 7, dpi = 300)
nolinker_pie.gg + ggtitle("STAU1 hybrids")

# PARIS

paris.feat.df <- paris.forgi.dt %>% replace(is.na(.), 0) # replqce NA with zero to simplify filtering

paris.feat.df <- paris.feat.df %>%
  group_by(id) %>%
  mutate(stem_type = case_when(!any(element_type == "i") ~ "Uninterrupted stem",
                               any(element_type == "i") & (!any(element_type == "i" & (L_width > 1 | R_width > 1))) &
                                 (!any(element_type == "i" & L_width != R_width)) ~ "Stem with only symmetrical bulges",
                               (any(element_type == "i")) & (!any(element_type == "i" & (L_width > 1 | R_width > 1))) &
                                 (any(element_type == "i" & L_width != R_width)) ~ "Stem with non-symmetrical bulges",
                               (any(element_type == "i")) &
                                 (any(element_type == "i" & (L_width > 1) & (R_width == 0 | R_width >= 1))) |
                                 (any(element_type == "i" & (L_width = 1) & (R_width > 1))) ~ "Stem with internal loops"))


paris_pie.gg <- draw_pie_chart(paris.feat.df, stem_type)
ggsave(paste0("paris", "_stem_types_pie.pdf"), nolinker_pie.gg, height = 11, width = 7, dpi = 300)

paris_pie.gg + ggtitle("PARIS hybrids")

# Nonhybrids

nornase.forgi.gg <- draw_pie_chart(nornase.forgi.dt, stem_type)
ggsave("non_hyb_stem_types_pie.pdf", nornase.forgi.gg, dpi = 300, height = 11, width = 7)

nornase.forgi.gg + ggtitle("STAU1 nonhybrids")


filtered.nonhyb.forgi.gg <- draw_pie_chart(filtered.nonhyb.forgi.df, stem_type)
ggsave("filtered_non_hyb_stem_types_pie.pdf", filtered.nonhyb.forgi.gg, dpi = 300, height = 11, width = 7)

filtered.nonhyb.forgi.gg + ggtitle(paste0("STAU1 nonhybrids ", 8, "-", max," bp long"))

Stem lengths

# paris.dt$total_paired
nolinker.feat.df <- analyse_duplexes(nolinker.feat.df)
Joining, by = "id"
paris.feat.df <- analyse_duplexes(paris.feat.df)
Joining, by = "id"
nolinker_features.df <- get_id_features(nolinker.feat.df)
nonhyb_features.df <- get_id_features(nornase.forgi.dt)
filtered_nonhyb_features.df <- get_id_features(filtered.nonhyb.forgi.df)
paris_features.df <- get_id_features(paris.feat.df)

filtered_nonhyb_features.df$Experiment <- "STAU1 nonhybrids (filtered)"
nolinker_features.df$Experiment <- "STAU1 hybrids"
nonhyb_features.df$Experiment <- "STAU1 nonhybrids"
paris_features.df$Experiment <- "PARIS"

all.features.df <- bind_rows(nolinker_features.df, filtered_nonhyb_features.df, nonhyb_features.df, paris_features.df)
all.features.df <- rowid_to_column(all.features.df)
all.features.df <- all.features.df %>% 
  gather("feature", "counts", max_duplex:iloop_counts)

Duplex features

all_features.gg <- ggplot(transform(all.features.df, feature = factor(feature, levels = c("sum_paired", "max_duplex","iloop_counts"))),
                         aes(x = "", y=counts, fill=Experiment)) +
  geom_boxplot() +
  theme(axis.text.x = element_text(angle = 90)) +
  scale_fill_brewer(palette="Blues") +
  facet_wrap(. ~ feature, scales = "free") +
  theme(legend.position="right", legend.direction="vertical")

all_features.gg

# ggsave("feature_comparison_boxplot.pdf", all_features.gg, dpi = 300)

Nucleotide frequency analysis

paris.dt <- paris.dt %>%
  dplyr::select(-cluster_name)
nolinker_nuc_freq.df
nolinker_nuc_freq.df <- get_nucleotide_frequency(nolinker.dt)
New names:
* A -> A...41
* C -> C...42
* G -> G...43
* T -> T...44
* A -> A...45
* ...
nolinker_nuc_freq.gg <- plot_nucleotide_frequency(nolinker_nuc_freq.df)

paris_nuc_freq.df <- get_nucleotide_frequency(paris.dt)
New names:
* A -> A...41
* C -> C...42
* G -> G...43
* T -> T...44
* A -> A...45
* ...
paris_nuc_freq.gg <- plot_nucleotide_frequency(paris_nuc_freq.df)

nonnhyb_nuc_freq.gg <- plot_nucleotide_frequency(filtered.nonhyb.forgi.df)


nolinker_nuc_freq.gg + ggtitle("STAU1")

nonnhyb_nuc_freq.gg + ggtitle("STAU1 nonhybrids")

paris_nuc_freq.gg + ggtitle("PARIS")

Cluster analyses

These analyses were performed on the clustered hybrids produced with the Tosca pipeline.

STAU1 hybrids data: Get genomic coordinates and annotate hybrids


hybrids.dt <- hybrids.dt[grep("^rRNA", L_seqnames, invert = TRUE)] # Remove rRNA
hybrids.dt <- hybrids.dt[grep("Mt", L_seqnames, invert = TRUE)]

# Reformat names of hybrids seqnames so they match the annotation gtf
hybrids.dt[, c("L_seqnames") := tstrsplit(L_seqnames, "::", fixed=TRUE)[1]]
hybrids.dt[, c("R_seqnames") := tstrsplit(L_seqnames, "::", fixed=TRUE)[1]]

# Convert to genomic coordinates
hybrids.dt <- convert_coordinates(hybrids.dt, genes.gr)
fwrite(hybrids.dt, "clusters.gc.tsv.gz", sep = "\t")

# Annotate hybrids
hybrids.dt <- annotate_hybrids(hybrids.dt, regions.gr)
fwrite(hybrids.dt, "annotated_clusters.gc.tsv.gz", sep = "\t")

hybrids.dt$Experiment <- "STAU1 - No linker"

PARIS data: Get genomic coordinates and annotate hybrids


paris_clusters.dt <- paris_clusters.dt[grep("^rRNA", L_seqnames, invert = TRUE)] # Remove rRNA
paris_clusters.dt <- paris_clusters.dt[grep("Mt", L_seqnames, invert = TRUE)]

paris_clusters.dt[, c("L_seqnames") := tstrsplit(L_seqnames, "::", fixed=TRUE)[1]]
paris_clusters.dt[, c("R_seqnames") := tstrsplit(L_seqnames, "::", fixed=TRUE)[1]]

# Convert to genomic coordinates
paris_clusters.dt <- convert_coordinates(paris_clusters.dt, genes.gr)
fwrite(paris_clusters.dt, "paris_clusters.gc.tsv.gz", sep = "\t")

# Annotate hybrids
paris_clusters.dt <- annotate_hybrids(paris_clusters.dt, regions.gr)
fwrite(paris_clusters.dt, "paris_annotated_clusters.gc.tsv.gz", sep = "\t")
paris_clusters.dt$Experiment <- "PARIS"
# add col cluster_name because PARIS data does not have unique cluster identifiers
paris_clusters.dt <- paris_clusters.dt %>% unite("cluster_name", c(name, L_seqnames),
                                           na.rm = TRUE, remove = FALSE, sep="_")
  
  
  
hybrids.dt <- hybrids.dt %>% unite("cluster_name", c(name, L_seqnames),
                                           na.rm = TRUE, remove = FALSE, sep="_")

Merge experiments

all_exp.df <- as.data.frame(rbind(hybrids.dt, paris_clusters.dt))
# intramolecular only
all_exp.df <- all_exp.df %>% dplyr::filter(L_gene_name == R_gene_name)

Transcript biotypes

biotype_counts.df <- all_exp.df %>%
  dplyr::select(cluster_name, Experiment, L_biotype, R_biotype) %>%
  pivot_longer(c(L_biotype, R_biotype), names_to = "arm", values_to = "biotype")

biotype_counts.df <- biotype_counts.df %>%
  group_by(Experiment, biotype) %>%
  mutate(biotype = case_when(str_detect(biotype, "lncRNA") ~ "lncRNA",
                             TRUE ~ biotype)) %>%
  summarise(counts = n()) %>%
  arrange(desc(biotype)) %>%
  mutate(percentage = counts*100 / sum(counts)) %>%
  ungroup()

biotype_counts.df <- biotype_counts.df %>%
  mutate(RNA_biotype = case_when((percentage < 0.05) ~ "Other",
                                 (percentage >= 0.05) ~ biotype))

biotype_counts.df <- biotype_counts.df %>%
  group_by(Experiment, RNA_biotype) %>%
  mutate(biotype_percentage = sum(percentage)) %>%
  ungroup()
# Simple bar-chart

ggplot(distinct(biotype_counts.df, across(c(Experiment, RNA_biotype, biotype_percentage))),
       aes(RNA_biotype, biotype_percentage)) + 
  geom_bar(position="dodge", stat = "identity", aes(fill = Experiment), alpha = 0.8) +
  scale_fill_brewer(palette="Dark2") +
  #scale_fill_paletteer_d("rcartocolor::Earth", direction = -1) +
  theme(legend.position="right", legend.direction="vertical",
        legend.title = element_blank()) +
  theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank()) +
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1)) +
  ylab("Percentage") +
  xlab("RNA biotype")

mRNA transcript regions

all_exp_mRNA.df <- all_exp.df %>% dplyr::filter(R_biotype  == "mRNA" & L_biotype == "mRNA")

all_exp_mRNA.df <- all_exp_mRNA.df %>%
  mutate(hybrid_type = case_when((L_region == "UTR3" & R_region == "UTR3") ~ "3'UTR-3'UTR",
                                 (L_region == "CDS" & R_region == "CDS") ~ "CDS-CDS",
                                 (L_region == "UTR3" & R_region == "CDS") ~ "3'UTR-CDS",
                                 (L_region == "CDS" & R_region == "UTR3") ~ "3'UTR-CDS",
                                 (L_region == "UTR5" & R_region == "UTR5") ~ "5'UTR-5'UTR",
                                 (L_region == "UTR3" & R_region == "UTR5") ~ "3'UTR-5'UTR",
                                 (L_region == "UTR5" & R_region == "UTR3") ~ "3'UTR-5'UTR",
                                 (L_region == "UTR5" & R_region == "CDS") ~ "5'UTR-CDS",
                                 (L_region == "CDS" & R_region == "UTR5") ~ "5'UTR-CDS"))


plot_stacked_barchart(all_exp_mRNA.df, hybrid_type, Experiment)

3’UTR hybrids

# Get hybrid spans; Get hybrid arm widths

threeutr.df <- all_exp_mRNA.df %>%
  dplyr::filter(L_region == "UTR3" & R_region == "UTR3")

threeutr.df <- threeutr.df %>%
  rowwise() %>%
  mutate(hybrid_span = R_start - L_end + 1, 
         L_arm_width = L_end - L_start + 1,
         R_arm_width = R_end - R_start + 1)

Hybrid span

ggplot(threeutr.df, aes(x = hybrid_span, color = Experiment)) + 
  geom_density(alpha = 0.8) +
  scale_colour_brewer(palette="Dark2") +
  scale_x_log10() + annotation_logticks() +
  theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank()) +
  ylab("Density") +
  xlab("Hybrid span")

Arm width

# convert to long format
threeutr_long.df <- threeutr.df %>%
  pivot_longer(c(R_arm_width, L_arm_width), names_to = "arm", values_to = "arm_width")

ggplot(threeutr_long.df, aes(x = arm, y = arm_width, fill = Experiment)) +
  geom_boxplot(alpha = 0.7) +
  theme(axis.text.x = element_text(angle = 90)) +
  scale_fill_brewer(palette="Dark2") +
  theme(legend.position="right", legend.direction="vertical") +
  theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank()) +
  ylab("Hybrid arm width") +
  xlab("Arm")
LS0tCnRpdGxlOiAiRHVwbGV4IGZlYXR1cmVzIGFuYWx5c2lzIgphdXRob3I6ICJJcmEgSW9zdWIiCmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVkICVCICVZJylgIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6ICczJwogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6IDMKICAgIHRvY19mbG9hdDogeWVzCiAgICB0aGVtZTogdW5pdGVkCiAgICBoaWdobGlnaHQ6IHRhbmdvCiAgICBkZl9wcmludDogcGFnZWQKICAgIGNvZGVfZm9sZGluZzogc2hvdwotLS0KCiMjIyMgTGlicmFyaWVzCmBgYHtyIGluY2x1ZGU9RkFMU0V9CmxpYnJhcnkocnRyYWNrbGF5ZXIpCmxpYnJhcnkocHJpbWF2ZXJhKQpsaWJyYXJ5KGRhdGEudGFibGUpCmxpYnJhcnkocGFsZXR0ZWVyKQpsaWJyYXJ5KGdncHVicikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGdncmVwZWwpCmxpYnJhcnkoZm9yY2F0cykKbGlicmFyeShzY2FsZXMpCmxpYnJhcnkodGlkeXZlcnNlLCB3YXJuLmNvbmZsaWN0cyA9IEZBTFNFKQpsaWJyYXJ5KHN0cmluZ3IpCmxpYnJhcnkoc3RyaW5naSkKbGlicmFyeShCaW9zdHJpbmdzKQpsaWJyYXJ5KHRpY3RvYykKbGlicmFyeShnZ3B1YnIpCnRoZW1lX3NldCh0aGVtZV9idygpICsKICAgICAgICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIpKQpgYGAKCiMjIyMgRnVuY3Rpb25zCmBgYHtyfQoKZHJhd19waWVfY2hhcnQgPC0gZnVuY3Rpb24oZGF0YS5kZiwgY29sdW1uKSB7CiAgZGF0YV9jb3VudHMuZGYgPC0gZGF0YS5kZiAlPiUKICAgIGRwbHlyOjpzZWxlY3QoaWQsIHt7Y29sdW1ufX0pICU+JQogICAgZGlzdGluY3QoKSAlPiUKICAgIGdyb3VwX2J5KHt7Y29sdW1ufX0pICU+JQogICAgc3VtbWFyaXNlKGNvdW50cyA9IG4oKSkgJT4lCiAgICBhcnJhbmdlKGRlc2MoY291bnRzKSkgJT4lCiAgICBtdXRhdGUocGVyY2VudGFnZSA9IHBlcmNlbnQoY291bnRzIC8gc3VtKGNvdW50cykpKSAKICAKICBwaWUgPC0gZ2dwbG90KGRhdGFfY291bnRzLmRmLCBhZXMoeD0iIiwgeT1jb3VudHMsIGZpbGw9e3tjb2x1bW59fSkpICsKICAgIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCB3aWR0aCA9IDAuMDUsIGNvbG9yPSJibGFjayIpICsKICAgIGNvb3JkX3BvbGFyKCJ5Iiwgc3RhcnQ9MCkgKwogICAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJTZXQzIiwgZGlyZWN0aW9uID0gLTQpICsKICAgIGdlb21fbGFiZWxfcmVwZWwoYWVzKGxhYmVsID0gcGVyY2VudGFnZSksIHNpemU9My41LCBzaG93LmxlZ2VuZCA9IEYsIHBvc2l0aW9uID0gcG9zaXRpb25fc3RhY2sodmp1c3QgPSAuNSkpICsKICAgIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKHRpdGxlID0gIlN0ZW0gdHlwZSIpKSArCiAgICBnZ3RpdGxlKHBhc3RlMCgiU3RlbSB0eXBlcyAtICIsIHN1bShkYXRhX2NvdW50cy5kZiRjb3VudHMpLCIgZHVwbGV4ZXMiKSkgKwogICAgdGhlbWVfdm9pZCgpICMgcmVtb3ZlIGJhY2tncm91bmQsIGdyaWQsIG51bWVyaWMgbGFiZWxzCiAgcmV0dXJuKHBpZSkKfQoKCiMgZ2V0X3N0ZW1fbGVuZ3RoIDwtIGZ1bmN0aW9uKGRhdGEuZGYpIHsKIyAgIGRhdGFfc2guZGYgPC0gZGF0YS5kZiAlPiUKIyAgICAgZ3JvdXBfYnkoaWQpICU+JQojICAgICBkcGx5cjo6ZmlsdGVyKGVsZW1lbnQgPT0gInMwIiB8IGVsZW1lbnQgPT0gImgwIikgJT4lICAjdXNlIHRoZXNlIHBvc2l0aW9ucyB0byBlc3RpbWF0ZSBzdGVtIGxlbmd0aAojICAgICBtdXRhdGUoc3RlbV9sZW5ndGhfTCA9IExfc3RhcnQgLSBsYWcoTF9zdGFydCksCiMgICAgICAgICAgICBzdGVtX2xlbmd0aF9SID0gbGFnKFJfZW5kKSAtIExfZW5kKSAlPiUKIyAgICAgZHBseXI6OmZpbHRlcihlbGVtZW50ID09ICJoMCIpICU+JQojICAgICBkcGx5cjo6c2VsZWN0KGlkLCBzdGVtX2xlbmd0aF9MLCBzdGVtX2xlbmd0aF9SKQojICAgZGF0YS5kZiA8LSBsZWZ0X2pvaW4oZGF0YS5kZiwgZGF0YV9zaC5kZiwgYnkgPSAiaWQiKQojICAgcmV0dXJuKGRhdGEuZGYpCiMgfQoKCiMgZ2V0X2hhaXJwaW5fbGVuZ3RoIDwtIGZ1bmN0aW9uKGRhdGEuZGYpIHsKIyAgIGRhdGFfaC5kZiA8LSBkYXRhLmRmICU+JSAKIyAgICAgZ3JvdXBfYnkoaWQpICU+JQojICAgICBkcGx5cjo6ZmlsdGVyKGVsZW1lbnQgPT0gImgwIikgJT4lCiMgICAgIG11dGF0ZShoYWlycGluX2xlbmd0aCA9IHN1bShMX3dpZHRoKSkgJT4lCiMgICAgIGRwbHlyOjpzZWxlY3QoaWQsIGhhaXJwaW5fbGVuZ3RoKQojICAgZGF0YS5kZiA8LSBsZWZ0X2pvaW4oZGF0YS5kZiwgZGF0YV9oLmRmLCBieSA9ICJpZCIpCiMgICByZXR1cm4oZGF0YS5kZikKIyB9CgoKZ2V0X3BhaXJlZF9yZWdpb25zIDwtIGZ1bmN0aW9uKGRhdGEuZGYpIHsKICBkdXBsZXhfbWF4LmRmIDwtIGRhdGEuZGYgJT4lCiAgICBncm91cF9ieShpZCkgJT4lCiAgICBkcGx5cjo6ZmlsdGVyKGVsZW1lbnRfdHlwZSA9PSAicyIpICU+JQogICAgc3VtbWFyaXNlKG1heF9kdXBsZXggPSBtYXgoTF93aWR0aCksCiAgICAgICAgICAgICAgc3VtX3BhaXJlZCA9IHN1bShMX3dpZHRoKSkgJT4lCiAgICBkcGx5cjo6c2VsZWN0KGlkLCBtYXhfZHVwbGV4LCBzdW1fcGFpcmVkKQogIGRhdGEuZGYgPC0gbGVmdF9qb2luKGRhdGEuZGYsIGR1cGxleF9tYXguZGYpCiAgCn0KCgpjb3VudF9lbGVtZW50IDwtIGZ1bmN0aW9uKGRhdGEuZGYsIHN0cnVjdHVyZV90eXBlKSB7CiAgY291bnRlciA8LSBkYXRhLmRmICU+JSAKICAgIGdyb3VwX2J5KGlkKSAlPiUgCiAgICBkcGx5cjo6ZmlsdGVyKGVsZW1lbnRfdHlwZSA9PSBzdHJ1Y3R1cmVfdHlwZSkgJT4lCiAgICBzdW1tYXJpc2UoY291bnQ9bigpKQogIHJldHVybihjb3VudGVyKQp9CgoKYW5hbHlzZV9kdXBsZXhlcyA8LSBmdW5jdGlvbihkYXRhLmRmKSB7CiAgZGF0YS5kZiA8LSBnZXRfcGFpcmVkX3JlZ2lvbnMoZGF0YS5kZikKICAjIGRhdGEuZGYgPC0gZ2V0X3N0ZW1fbGVuZ3RoKGRhdGEuZGYpCiAgIyBkYXRhLmRmIDwtIGdldF9oYWlycGluX2xlbmd0aChkYXRhLmRmKQogICNkYXRhLmRmIDwtIGdldF9pbG9vcF9sZW5ndGhfc3VtKGRhdGEuZGYpCiAgCiAgaWxvb3BfY291bnRzLmRmIDwtIGNvdW50X2VsZW1lbnQoZGF0YS5kZiwgImkiKSAlPiUgCiAgICBkcGx5cjo6c2VsZWN0KGlkLCBjb3VudCkgJT4lCiAgICBkcGx5cjo6cmVuYW1lKGlsb29wX2NvdW50cyA9IGNvdW50KQogIGRhdGEuZGYgPC0gbGVmdF9qb2luKGRhdGEuZGYsIGlsb29wX2NvdW50cy5kZiwgYnkgPSAiaWQiKQogIHJldHVybihkYXRhLmRmKQp9CgoKZ2V0X2lkX2ZlYXR1cmVzIDwtIGZ1bmN0aW9uKGRhdGEuZGYpIHsKICBpZF9mZWF0dXJlcy5kZiA8LSBkYXRhLmRmICU+JQogICAgZHBseXI6OnNlbGVjdChpZCwgbWF4X2R1cGxleCwgc3VtX3BhaXJlZCwgaWxvb3BfY291bnRzKSAlPiUKICAgIGRpc3RpbmN0KCkgJT4lCiAgICByZXBsYWNlKGlzLm5hKC4pLCAwKQogIHJldHVybihpZF9mZWF0dXJlcy5kZikKfQoKCmBgYAoKCgpgYGB7cn0KcGxvdF9zdGFja2VkX2JhcmNoYXJ0IDwtIGZ1bmN0aW9uKGRhdGEuZGYsIGNvbHVtbiwgZ3JvdXApIHsKICAKICBkYXRhX2NvdW50cy5kZiA8LSBkYXRhLmRmICU+JQogICAgZHBseXI6OnNlbGVjdChuYW1lLCB7e2dyb3VwfX0sIHt7Y29sdW1ufX0pICU+JQogICAgZ3JvdXBfYnkoe3tncm91cH19LCB7e2NvbHVtbn19KSAlPiUKICAgIHN1bW1hcmlzZShjb3VudHMgPSBuKCkpICU+JQogICAgYXJyYW5nZShkZXNjKHt7Y29sdW1ufX0pKSAlPiUKICAgIG11dGF0ZShwZXJjZW50YWdlID0gY291bnRzKjEwMCAvIHN1bShjb3VudHMpKSAlPiUKICAgIG11dGF0ZShwb3MgPSBjdW1zdW0ocGVyY2VudGFnZSkgLSBwZXJjZW50YWdlLzIpCiAgCiAgYmFyID0gZ2dwbG90KCkgKyBnZW9tX2JhcihhZXMoeSA9IHBlcmNlbnRhZ2UsIHggPSAiIiwgZmlsbCA9IHt7Y29sdW1ufX0pLCBhbHBoYSA9IDAuOCwgZGF0YSA9IGRhdGFfY291bnRzLmRmLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RhdD0iaWRlbnRpdHkiLCB3aWR0aCA9IDAuNSkgKwogICAgZ2VvbV90ZXh0KGRhdGEgPSBkYXRhX2NvdW50cy5kZiwgYWVzKHggPSAiIiwgeSA9IHBvcywgbGFiZWwgPSBwYXN0ZTAocm91bmQocGVyY2VudGFnZSwxKSwiJSIpKSwgc2l6ZT0zKSArCiAgICBmYWNldF9ncmlkKGNvbHMgPSB2YXJzKHt7Z3JvdXB9fSkpICsKICAgICNmYWNldF93cmFwKC4gfiBFeHBlcmltZW50LCBzY2FsZXMgPSAiZnJlZSIpICsKICAgIHNjYWxlX2ZpbGxfcGFsZXR0ZWVyX2QoInJjYXJ0b2NvbG9yOjpFYXJ0aCIsIGRpcmVjdGlvbiA9IC0xKSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb249InJpZ2h0IiwgbGVnZW5kLmRpcmVjdGlvbj0idmVydGljYWwiLAogICAgICAgICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArCiAgICB0aGVtZShwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpKSArCiAgICB5bGFiKCJQZXJjZW50YWdlIikgKwogICAgeGxhYigiRXhwZXJpbWVudCIpCiAgcmV0dXJuKGJhcikKICAKfQpgYGAKCgpgYGB7cn0KZ2V0X251Y2xlb3RpZGVfZnJlcXVlbmN5IDwtIGZ1bmN0aW9uKGR1cGxleGVzLmRmKSB7CiAgCiAgZHVwbGV4ZXMuZGYgPC0gZHVwbGV4ZXMuZGYgJT4lCiAgICBkcGx5cjo6ZmlsdGVyKCFpcy5uYShsX3BhaXJlZF9yZXNpZHVlcykpICU+JQogICAgZHBseXI6OmZpbHRlcighaXMubmEocl9wYWlyZWRfcmVzaWR1ZXMpKQogIAogIExfc2VxIDwtIEROQVN0cmluZ1NldChkdXBsZXhlcy5kZiRsX3BhaXJlZF9yZXNpZHVlcykKICBSX3NlcSA8LSBETkFTdHJpbmdTZXQoZHVwbGV4ZXMuZGYkcl9wYWlyZWRfcmVzaWR1ZXMpCiAgCiAgTF9mcmVxIDwtIGFzLmRhdGEuZnJhbWUob2xpZ29udWNsZW90aWRlRnJlcXVlbmN5KExfc2VxLCB3aWR0aCA9IDEsIGFzLnByb2I9VFJVRSkpCiAgUl9mcmVxIDwtIGFzLmRhdGEuZnJhbWUob2xpZ29udWNsZW90aWRlRnJlcXVlbmN5KFJfc2VxLCB3aWR0aCA9IDEsIGFzLnByb2I9VFJVRSkpCiAgZHVwbGV4ZXMuZGYgPC0gZHBseXI6OmJpbmRfY29scyhkdXBsZXhlcy5kZiwgTF9mcmVxLCBSX2ZyZXEpCiAgCiAgY29sbmFtZXMoZHVwbGV4ZXMuZGYpID0gZ3N1YigiLi4uNDF8Li4uNDJ8Li4uNDN8Li4uNDQiLCAiX0wiLCBjb2xuYW1lcyhkdXBsZXhlcy5kZikpICMgcmVuYW1lIGNvbHVtbnMKICBjb2xuYW1lcyhkdXBsZXhlcy5kZikgPSBnc3ViKCIuLi40NXwuLi40NnwuLi40N3wuLi40OCIsICJfUiIsIGNvbG5hbWVzKGR1cGxleGVzLmRmKSkKICAKICAjIFB1ciBjb250ZW50IC0gY2FsY3VsYXRlIG1heCBwdXIgTCB2cy4gUiBhcm0gCiAgZHVwbGV4ZXMuZGYgPC0gZHVwbGV4ZXMuZGYgJT4lCiAgICByb3d3aXNlKCkgJT4lIAogICAgbXV0YXRlKExfcHVyID0gc3VtKGMoQV9MLCBHX0wpKSwgIyBwdXJpbmUgdG90YWwgcGVyIGh5YnJpZAogICAgICAgICAgIFJfcHVyID0gc3VtKGMoQV9SLCBHX1IpKSkgJT4lCiAgICBtdXRhdGUobWF4X3B1ciA9IG1heChMX3B1ciwgUl9wdXIpKSAlPiUKICAgIG11dGF0ZShhcm0gPSBjYXNlX3doZW4oKG1heF9wdXIgPT0gTF9wdXIpIH4gIkwiLCAjIGFzc2lnbiBtYXhpbWFsIHB1cmluZSBhcm0KICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+ICJSIikpICU+JQogICAgdW5ncm91cCgpIAogIAogICMgcmVvcmllbnQgZHVwbGV4IGZyZXF1ZW5jaWVzIGJhc2VkIG9uIG1heGltYWwgcHVyaW5lIGNvbnRlbnQgKGNvbHVtbiAiYXJtIikKICBudWNfZnJlcV9tYXhfcHVyX2FybS5kZiA8LSBkdXBsZXhlcy5kZiAlPiUKICAgIGRwbHlyOjpmaWx0ZXIoYXJtID09ICJMIikgJT4lCiAgICBtdXRhdGUoQV9mcmVxX21heF9wdXJfYXJtID0gQV9MLCBBX2ZyZXFfbWluX3B1cl9hcm0gPSBBX1IsCiAgICAgICAgICAgR19mcmVxX21heF9wdXJfYXJtID0gR19MLCBHX2ZyZXFfbWluX3B1cl9hcm0gPSBHX1IsCiAgICAgICAgICAgQ19mcmVxX21heF9wdXJfYXJtID0gQ19MLCBDX2ZyZXFfbWluX3B1cl9hcm0gPSBDX1IsCiAgICAgICAgICAgVV9mcmVxX21heF9wdXJfYXJtID0gVF9MLCBVX2ZyZXFfbWluX3B1cl9hcm0gPSBUX1IpCgogIG51Y19mcmVxX21pbl9wdXJfYXJtLmRmIDwtIGR1cGxleGVzLmRmICU+JQogICAgZHBseXI6OmZpbHRlcihhcm0gPT0gIlIiKSAlPiUKICAgIG11dGF0ZShBX2ZyZXFfbWF4X3B1cl9hcm0gPSBBX1IsIEFfZnJlcV9taW5fcHVyX2FybSA9IEFfTCwKICAgICAgICAgICBHX2ZyZXFfbWF4X3B1cl9hcm0gPSBHX1IsIEdfZnJlcV9taW5fcHVyX2FybSA9IEdfTCwKICAgICAgICAgICBDX2ZyZXFfbWF4X3B1cl9hcm0gPSBDX1IsIENfZnJlcV9taW5fcHVyX2FybSA9IENfTCwKICAgICAgICAgICBVX2ZyZXFfbWF4X3B1cl9hcm0gPSBUX1IsIFVfZnJlcV9taW5fcHVyX2FybSA9IFRfTCkKCiAgbnVjX2ZyZXFfcmVvcmRlcmVkLmRmIDwtIHJiaW5kKG51Y19mcmVxX21heF9wdXJfYXJtLmRmLCBudWNfZnJlcV9taW5fcHVyX2FybS5kZikKICBudWNfZnJlcV9yZW9yZGVyZWQuZGYgPC0gbnVjX2ZyZXFfcmVvcmRlcmVkLmRmICU+JSBhcnJhbmdlKGRlc2MobWF4X3B1cikpCiAgCiAgcmV0dXJuKG51Y19mcmVxX3Jlb3JkZXJlZC5kZikKICAKfQoKYGBgCgoKYGBge3J9CnBsb3RfbnVjbGVvdGlkZV9mcmVxdWVuY3kgPC0gZnVuY3Rpb24oZGF0YSkgewogIAogIGRhdGEgPC0gZGF0YSAlPiUKICAgIGRwbHlyOjpzZWxlY3QoaWQsIEFfZnJlcV9tYXhfcHVyX2FybSwgR19mcmVxX21heF9wdXJfYXJtLCBDX2ZyZXFfbWF4X3B1cl9hcm0sIFVfZnJlcV9tYXhfcHVyX2FybSwKICAgICAgICAgICAgICAgICAgQV9mcmVxX21pbl9wdXJfYXJtLCBHX2ZyZXFfbWluX3B1cl9hcm0sIENfZnJlcV9taW5fcHVyX2FybSwgVV9mcmVxX21pbl9wdXJfYXJtKSAlPiUKICAgIGRwbHlyOjpkaXN0aW5jdCgpICU+JQogICAgZHJvcF9uYSgpICU+JQogICAgZHBseXI6OmFycmFuZ2UoR19mcmVxX21heF9wdXJfYXJtKQogIAogICMgUHJlcGFyZSBmb3IgcGxvdHRpbmcKICBoaWdoX3B1ci5kZiA8LSBkYXRhICU+JSAKICAgIGRwbHlyOjpzZWxlY3QoaWQsIEFfZnJlcV9tYXhfcHVyX2FybSwgR19mcmVxX21heF9wdXJfYXJtLCBDX2ZyZXFfbWF4X3B1cl9hcm0sIFVfZnJlcV9tYXhfcHVyX2FybSkKICBoaWdoX3B1ci5kZiA8LSByb3dpZF90b19jb2x1bW4oaGlnaF9wdXIuZGYpCiAgbG9uZ19oaWdoX3B1ci5kZiA8LSBoaWdoX3B1ci5kZiAlPiUgCiAgICBnYXRoZXIocmVzaWR1ZSwgZnJlcXVlbmN5LCBBX2ZyZXFfbWF4X3B1cl9hcm06VV9mcmVxX21heF9wdXJfYXJtKQogIGxvbmdfaGlnaF9wdXIuZGYkYXJtID0gIkhpZ2ggcHVyaW5lIGFybSIKICAKICBsb3dfcHVyLmRmIDwtIGRhdGEgJT4lIAogICAgZHBseXI6OnNlbGVjdChpZCwgQV9mcmVxX21pbl9wdXJfYXJtLCBHX2ZyZXFfbWluX3B1cl9hcm0sIENfZnJlcV9taW5fcHVyX2FybSwgVV9mcmVxX21pbl9wdXJfYXJtKQoKICBsb3dfcHVyLmRmIDwtIHJvd2lkX3RvX2NvbHVtbihsb3dfcHVyLmRmKQogIGxvbmdfbG93X3B1ci5kZiA8LSBsb3dfcHVyLmRmICU+JSAKICAgIGdhdGhlcihyZXNpZHVlLCBmcmVxdWVuY3ksIEFfZnJlcV9taW5fcHVyX2FybTpVX2ZyZXFfbWluX3B1cl9hcm0pCiAgbG9uZ19sb3dfcHVyLmRmJGFybSA9ICJMb3cgcHVyaW5lIGFybSIKICAKICBudWNfZnJlcS5kZiA8LSByYmluZChsb25nX2hpZ2hfcHVyLmRmLCBsb25nX2xvd19wdXIuZGYpCiAgbnVjX2ZyZXEuZGYkcmVzaWR1ZSA8LSBzdHJfc3ViKG51Y19mcmVxLmRmJHJlc2lkdWUsIDEsMSkgI3RyaW0gc3RyaW5nIHRvIGdldCBudWNsZW90aWRlIChmaXJzdCkgbGV0dGVyCiAgCiAgIyBQbG90IGluZGl2aWR1YWwgbnVjbGVvdGlkZSBmcmVxdWVuY2llcwogIG51Y19mcmVxLmdnIDwtIGdncGxvdChudWNfZnJlcS5kZiwgYWVzKHg9cm93aWQsIHk9ZnJlcXVlbmN5LCBjb2xvciA9IHJlc2lkdWUpKSArCiAgICBnZW9tX3Ntb290aChzZT1GKSArIGNvb3JkX2ZsaXAoKSArCiAgICBmYWNldF9ncmlkKGNvbHMgPSB2YXJzKGFybSkpICsKICAgIHlsYWIoIkZyZXF1ZW5jeSIpICsKICAgIHhsYWIoIklEIikgKwogICAgeWxpbSgwLCAxKSArCiAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygiZGFya2dyZWVuIiwiZG9kZ2VyYmx1ZTQiLCJnb2xkZW5yb2QzIiwiZmlyZWJyaWNrIikpCiAgCiAgcmV0dXJuKG51Y19mcmVxLmdnKQogIAp9CmBgYAoKCmBgYHtyfQpydW5fcm5hZXZhbCA8LSBmdW5jdGlvbihmYXN0YS5maWxlLCBhcmdzID0gIGMoIi1pIiwgcGFzdGUwKGZhc3RhLmZpbGUsIj50ZW1wLmZhIikpKSB7CiAgCiAgcm5hZXZhbC5vdXQgPC0gc3lzdGVtMihjb21tYW5kID0gIlJOQWV2YWwiLCBhcmdzID0gYXJncywgc3Rkb3V0ID0gVFJVRSkKICBybmFldmFsLmRmIDwtIGFzLmRhdGEuZnJhbWUocmVhZEJTdHJpbmdTZXQoInRlbXAuZmEiKSkKICBybmFldmFsLmRmIDwtIHJuYWV2YWwuZGYgJT4lIHJvd25hbWVzX3RvX2NvbHVtbih2YXIgPSAiaWQiKQogIAogIHJuYWV2YWwuZGYgPC0gcm5hZXZhbC5kZiAlPiUKICAgIHJvd3dpc2UoKSAlPiUKICAgIG11dGF0ZShldmFsX21mZSA9IGFzLm51bWVyaWMoc3RyX3N1Yih4LCAtNywtMikpKSAlPiUKICAgIHVuZ3JvdXAoKSAlPiUKICAgIGRwbHlyOjpzZWxlY3QoaWQsIGV2YWxfbWZlKQoKICByZXR1cm4ocm5hZXZhbC5kZikKICAKfQoKCmdldF9tZmUgPC0gZnVuY3Rpb24oZm9yZ2ksIHJuYWZvbGQsIHByZWZpeCkgewogIAogIGRhdGEuZGYgPC0gZm9yZ2kgJT4lCiAgICBkcGx5cjo6ZmlsdGVyKGVsZW1lbnRfdHlwZSA9PSAicyIpICU+JQogICAgZ3JvdXBfYnkoaWQpICU+JQogICAgbXV0YXRlKExfbGltaXQgPSBtaW4oTF9zdGFydCksCiAgICAgICAgICAgUl9saW1pdCA9IG1heChSX2VuZCkpICU+JQogICAgZHBseXI6OnNlbGVjdChpZCwgTF9saW1pdCwgUl9saW1pdCkgJT4lCiAgICBkaXN0aW5jdCgpICU+JQogICAgdW5ncm91cCgpCiAgCiAgZGF0YS5kZiA8LSBsZWZ0X2pvaW4oZGF0YS5kZiAsIHJuYWZvbGQsIGJ5ID0gImlkIikgIyBhZGQgdGhlIHNlcXVlbmNlIGFuZCBzdHJ1Y3R1cmUgdG8gdGhlIGZvcmdpCiAgZGF0YS5kZiA8LSBkYXRhLmRmICU+JQogICAgbXV0YXRlKHN0ZW1fbG9vcF9zZXEgPSBzdWJzdHIoc2VxdWVuY2UsIExfbGltaXQsIFJfbGltaXQpLAogICAgICAgICAgIHN0ZW1fbG9vcF9kYiA9IHN1YnN0cihtZWFfc3RydWN0dXJlLCBMX2xpbWl0LCBSX2xpbWl0KSkgJT4lCiAgICBkcGx5cjo6c2VsZWN0KC1zZXF1ZW5jZSwgLW1lYV9zdHJ1Y3R1cmUpCiAgIyB3cml0ZSBtdWx0aS1mYXN0YSB3aXRoIHNlcSBhbmQgZGIKICBmYXN0YSA8LSBjaGFyYWN0ZXIobnJvdyhkYXRhLmRmKSAqIDMpICMgZW1wdHkgZmlsZQogIGZhc3RhW2MoVFJVRSwgRkFMU0UsIEZBTFNFKV0gPC0gcGFzdGUwKCI+IiwgZGF0YS5kZiRpZCkKICBmYXN0YVtjKEZBTFNFLCBUUlVFLCBGQUxTRSldIDwtIGRhdGEuZGYkc3RlbV9sb29wX3NlcQogIGZhc3RhW2MoRkFMU0UsIEZBTFNFLCBUUlVFKV0gPC0gZGF0YS5kZiRzdGVtX2xvb3BfZGIKICAKICB3cml0ZUxpbmVzKGZhc3RhLCBwYXN0ZTAocHJlZml4LCJfcm5hZXZhbC5mYXN0YSIpKQogIG1mZS5kZiA8LSBydW5fcm5hZXZhbChwYXN0ZTAocHJlZml4LCJfcm5hZXZhbC5mYXN0YSIpKQogIGRhdGEuZGYgPC0gbGVmdF9qb2luKGRhdGEuZGYsIG1mZS5kZiwgYnkgPSAiaWQiKQogIGZvcmdpIDwtIGxlZnRfam9pbihmb3JnaSwgZGF0YS5kZiwgYnkgPSAiaWQiKQogIAogIGludmlzaWJsZShmaWxlLnJlbW92ZSgidGVtcC5mYSIpKQogIHJldHVybihmb3JnaSkKICAKfQoKYGBgCgojIyMjIERhdGEKCmBgYHtyfQojIE91dHB1dHMgZnJvbSBtZmUgYW5kIGZvcmdpIGFuYWx5c2VzCiNzZXR3ZCgifi9Eb2N1bWVudHMvcHJvamVjdHMvY29tcHV0YXRpb25hbF9oaUNMSVAvbm90ZWJvb2tzIikKbm9saW5rZXIuZHQgPC0gZnJlYWQoInN0YXUxXzN1dHJfbWlubWZlX2NsdXN0ZXJfaHlicmlkcy5tZmVfYW5hbHlzZXMudHN2Lmd6IikgIyBhbmFseXNlX25vbGlua2VyX2R1cGxleGVzLlIgb3V0cHV0Cm5vbGlua2VyX2ZvcmdpLmR0IDwtIGZyZWFkKCJzdGF1MV8zdXRyX21pbm1mZV9jbHVzdGVyX2h5YnJpZHMuZm9yZ2kudHN2Lmd6IikgIyBzaW1pbGFyIGJ1dCBmb3IgUEFSSVMKCnBhcmlzLmR0IDwtIGZyZWFkKCJwYXJpc18zdXRyX21pbm1mZV9jbHVzdGVyX2h5YnJpZHMubWZlX2FuYWx5c2VzLnRzdiIpCnBhcmlzLmZvcmdpLmR0IDwtIGZyZWFkKCJwYXJpc18zdXRyX21pbm1mZV9jbHVzdGVyX2h5YnJpZHMuZm9yZ2kudHN2Lmd6IikKCm5vcm5hc2UuZm9yZ2kuZHQgPC0gZnJlYWQoInN0YXUxLnBlYWtzLjEwbnRfMTBudF9mb3JnaV9hbmFseXNlcy5kZi50eHQiKQoKcm5hZm9sZC5kZiA8LSByZWFkLmNzdigiL1VzZXJzL2lvc3ViaS9Eb2N1bWVudHMvcHJvamVjdHMvY29tcHV0YXRpb25hbF9oaUNMSVAvbm9uaHlicmlkc18xMG50XzEwbnQvc3RhdTEucm5hZm9sZC50c3YuZ3oiLCBzZXAgPSAiXHQiKQpybmFmb2xkLmRmIDwtIHJvd2lkX3RvX2NvbHVtbihybmFmb2xkLmRmLCAiaWQiKQpybmFmb2xkLmRmJGlkIDwtIHBhc3RlMCgiSUQiLCBybmFmb2xkLmRmJGlkLCBzZXA9IiIpCnJuYWZvbGQuZGYgPC0gZHBseXI6OnNlbGVjdChybmFmb2xkLmRmLCBjKGlkLCBzZXF1ZW5jZSwgbWVhX3N0cnVjdHVyZSkpCmBgYAoKCmBgYHtyfQojIEFubm90YXRpb25zCmdlbmVzLmdyIDwtIHJ0cmFja2xheWVyOjppbXBvcnQuZ2ZmMigiL1VzZXJzL2lvc3ViaS9Eb2N1bWVudHMvcHJvamVjdHMvY29tcHV0YXRpb25hbF9oaUNMSVAvZmlsZXMvaHVtYW5fR2VuY29kZVYzMy5ndGYuZ3oiKQpyZWdpb25zLmdyIDwtIGltcG9ydC5nZmYyKCJ+L0RvY3VtZW50cy9HZW5vbWVzL2h1bWFuL3JlZ2lvbnMuZ3RmLmd6IikKCiMgQ2x1c3RlcnMgKFRvc2NhKQpoeWJyaWRzLmR0IDwtIGZyZWFkKCIvVXNlcnMvaW9zdWJpL0RvY3VtZW50cy9wcm9qZWN0cy9jb21wdXRhdGlvbmFsX2hpQ0xJUC9maWxlcy9hbGwuY2x1c3RlcnMudHN2Lmd6IikKcGFyaXNfY2x1c3RlcnMuZHQgPC0gZnJlYWQoIi9Vc2Vycy9pb3N1YmkvRG9jdW1lbnRzL3Byb2plY3RzL2NvbXB1dGF0aW9uYWxfaGlDTElQL2ZpbGVzL3BhcmlzL2FsbC5jbHVzdGVycy50c3YuZ3oiKQoKYGBgCgoKIyBNRkUgY29tcGFyaXNvbnMKClRoZXNlIGFuYWx5c2VzIHdlcmUgcGVyZm9ybWVkIG9uIHJlcHJlc2VudGF0aXZlIGh5YnJpZHMgcHJvZHVjZWQgd2l0aCB0aGUgVG9zY2EgcGlwZWxpbmUsIGFzIHdlbGwgYXMgcHJlZGljdGVkIGR1cGxleGVzIHVzaW5nIFNUQVUxIHBlYWtzIGFuZCBSTkFmb2xkCk9ubHkgaW50cmFtb2xlY3VsYXIgMydVVFIgZHVwbGV4ZXMgd2VyZSBhbmFseXNlZC4gRm9yIHRoZSBoeWJyaWQgZGF0YSwgdGhlIGh5YnJpZCB3aXRoIG1pbi4gTUZFIHdhcyBjaG9zZW4gdG8gcmVwcmVzZW50IGVhY2ggY2x1c3Rlci4KCiMjIyMgTm8gUk5hc2UgZHVwbGV4ZXMKYGBge3J9CiNTeXMuc2V0ZW52KFBBVEg9Ii9Vc2Vycy9pb3N1Ymkvb3B0L21pbmljb25kYTMvYmluLyIpCm5vbmh5YnJpZHMubWZlLmRmIDwtIGdldF9tZmUobm9ybmFzZS5mb3JnaS5kdCwgcm5hZm9sZC5kZiwgcHJlZml4PSJzdGF1MSIpCm5vbmh5YnJpZHMubWZlLmRmIDwtIG5vbmh5YnJpZHMubWZlLmRmICU+JQogIGRwbHlyOjpzZWxlY3QoaWQsIGV2YWxfbWZlKSAlPiUKICBkaXN0aW5jdCgpCiAgCgpub3JuYXNlLmZvcmdpLmR0IDwtIGxlZnRfam9pbihub3JuYXNlLmZvcmdpLmR0LCBub25oeWJyaWRzLm1mZS5kZiwgYnkgPSAiaWQiKQoKbm9uaHlicmlkcy5tZmUuZGYgPC0gbm9uaHlicmlkcy5tZmUuZGYgJT4lCiAgbXV0YXRlKFNhbXBsZSA9ICJtZmUiLCBFeHBlcmltZW50ID0gIlNUQVUxXG5ub25oeWJyaWRzIikKCm5vbmh5YnJpZHMubWZlLmRmIDwtIGRwbHlyOjpyZW5hbWUobm9uaHlicmlkcy5tZmUuZGYsIE1GRSA9IGV2YWxfbWZlKQogIApgYGAKCgojIyMjIE5vIGxpbmtlciBkdXBsZXhlcwpgYGB7cn0Kbm9saW5rZXIubWZlLmRmIDwtIG5vbGlua2VyLmR0ICU+JQogIGRwbHlyOjpzZWxlY3QoaWQsIG1mZSwgc2h1ZmZsZWRfbWZlKSAlPiUKICBkaXN0aW5jdCgpIAoKbm9saW5rZXIubWZlLmRmIDwtIG5vbGlua2VyLm1mZS5kZiAlPiUKICBnYXRoZXIoIlNhbXBsZSIsICJNRkUiLCBtZmU6c2h1ZmZsZWRfbWZlKSAlPiUKICBtdXRhdGUoRXhwZXJpbWVudCA9ICJTVEFVMSIpCmBgYAoKCgojIyMjIFBBUklTIGR1cGxleGVzCmBgYHtyfQpwYXJpcy5tZmUuZGYgPC0gcGFyaXMuZHQgJT4lCiAgZHBseXI6OnNlbGVjdChpZCwgbWZlLCBzaHVmZmxlZF9tZmUpICU+JQogIGRpc3RpbmN0KCkgCgpwYXJpcy5tZmUuZGYgPC0gcGFyaXMubWZlLmRmICU+JQogIGdhdGhlcigiU2FtcGxlIiwgIk1GRSIsIG1mZTpzaHVmZmxlZF9tZmUpICU+JQogIG11dGF0ZShFeHBlcmltZW50ID0gIlBBUklTIikKYGBgCgojIyMjIFB1bGwgZXhwZXJpbWVudHMgdG9nZXRoZXIKYGBge3J9CnN0YXUxLmh5Yi5ub25oeWIuZHQgPC0gYmluZF9yb3dzKG5vbGlua2VyLm1mZS5kZiwgcGFyaXMubWZlLmRmLCBub25oeWJyaWRzLm1mZS5kZikKCnN0YXUxLmh5Yi5ub25oeWIuZHQgPC0gc3RhdTEuaHliLm5vbmh5Yi5kdCAlPiUKICBtdXRhdGUoc2FtcGxlID0gY2FzZV93aGVuKChTYW1wbGUgPT0gIm1mZSIpIH4gIlNUQVUxIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIChTYW1wbGUgPT0gInNodWZmbGVkX21mZSIpIH4gIlNodWZmbGVkIGNvbnRyb2wiKSkKYGBgCgojIyMgTUZFIERlbnNpdHkgcGxvdHMKCmBgYHtyfQpzdGF1MS5oeWIubm9uaHliLmdnIDwtIGdncGxvdChzdGF1MS5oeWIubm9uaHliLmR0LCBhZXMoeCA9IE1GRSwgbGluZXR5cGUgPSBTYW1wbGUpKSArIAogIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuOCkgKwogIHNjYWxlX2NvbG91cl9icmV3ZXIocGFsZXR0ZT0iRGFyazIiKSArCiAgc2NhbGVfbGluZXR5cGVfbWFudWFsKHZhbHVlcz1jKCAic29saWQiLCAibG9uZ2Rhc2giKSkrCiAgZmFjZXRfZ3JpZChjb2xzID0gdmFycyhFeHBlcmltZW50KSkgKwogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCksIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCkpICsKICB5bGFiKCJEZW5zaXR5IikgKwogIHhsYWIoIk1GRSAoa2NhbC9tb2wpIikgKwogIGdndGl0bGUoIjMnVVRSIGNsdXN0ZXJzIG1pbiBNRkUiKQoKc3RhdTEuaHliLm5vbmh5Yi5nZwojIGdnc2F2ZSgiM1VUUl9jbHVzdGVyc19ub2xpbmtlcl9taW5NRkUucGRmIiwgc3RhdTEuaHliLm5vbmh5Yi5nZywgZHBpPTMwMCwgaGVpZ2h0ID0gNywgd2lkdGggPSA4KSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKYGBgCiMjIyBNRkUgVmlvbGluIHBsb3RzCgpgYGB7cn0KIyBhbGwKCnN0YXUxLmh5Yi5ub25oeWIuZHQgPC0gc3RhdTEuaHliLm5vbmh5Yi5kdCAlPiUKICBkcGx5cjo6ZmlsdGVyKFNhbXBsZSAhPSAiU2h1ZmZsZWQgY29udHJvbCIpCgptZmUudmlvbGluLmdnIDwtIGdncGxvdChzdGF1MS5oeWIubm9uaHliLmR0LCBhZXMoeCA9IEV4cGVyaW1lbnQsIHkgPSBNRkUsIGZpbGwgPSBFeHBlcmltZW50KSkgKwogIGdlb21fdmlvbGluKCkgKwogIGdlb21fYm94cGxvdCh3aWR0aD0wLjEpICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlPSJCbHVlcyIpICsKICAjc2NhbGVfY29sb3JfZ3JleSgpICsKICAjc3RhdF9zdW1tYXJ5KGZ1bi55PW1lZGlhbiwgZ2VvbT0icG9pbnQiLCBzaXplPTIsIGNvbG9yPSJncmV5IikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0icmlnaHQiLCBsZWdlbmQuZGlyZWN0aW9uPSJ2ZXJ0aWNhbCIsCiAgICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpICsKICAgIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCksIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCkpKwogIGdndGl0bGUoIjMnVVRSIGNsdXN0ZXJzIG1pbiBNRkUiKQoKbWZlLnZpb2xpbi5nZwpgYGAKIyMjIyBNRkUgb2Ygbm9uLWh5YnJpZHMgaXMgaGlnaGVyIHRoYW4gU1RBVTEgaHlicmlkcyBhbmQgUEFSSVMuIE5lZWQgdG8gZmlsdGVyIHRoZSBub24taHlicmlkcyBiYXNlZCBvbiB0aGUgbGVuZ3RoIG9mIHN0ZW1zIChwYWlyZWQgcmVzaWR1ZXMpCgpgYGB7cn0Kbm9saW5rZXJfZmVhdHVyZXMuZGYgPC0gbm9saW5rZXIuZHQgJT4lCiAgZHBseXI6OnNlbGVjdChpZCwgdG90YWxfcGFpcmVkKSAlPiUKICB1bmdyb3VwKCkKCm1heCA8LSBxdWFudGlsZShub2xpbmtlcl9mZWF0dXJlcy5kZiR0b3RhbF9wYWlyZWQsIDAuOTApW1sxXV0KbWluIDwtIHF1YW50aWxlKG5vbGlua2VyX2ZlYXR1cmVzLmRmJHRvdGFsX3BhaXJlZCwgMC4yKVtbMV1dCgptZWRpYW4obm9saW5rZXJfZmVhdHVyZXMuZGYkdG90YWxfcGFpcmVkKQoKYGBgCgoKIyMjIyBGaWx0ZXIgdGhlIG5vbi1oeWJyaWRzIGFjY29yZGluZyB0byB0aGUgc2l6ZSBvZiBwYWlyZWQgcmVzaWR1ZXMKYGBge3J9CmZpbHRlcmVkLm5vbmh5Yi5mb3JnaS5kZiA8LSBub3JuYXNlLmZvcmdpLmR0ICU+JQogIGRwbHlyOjpmaWx0ZXIoYmV0d2VlbihzdW1fcGFpcmVkLCA4LCBtYXgpICkKYGBgCgoKYGBge3J9CmZpbHRlcmVkLm5vbmh5Yi5tZmUuZGYgPC0gZmlsdGVyZWQubm9uaHliLmZvcmdpLmRmICU+JQogIGRwbHlyOjpzZWxlY3QoaWQsIGV2YWxfbWZlKSAlPiUKICBkaXN0aW5jdCgpICU+JQogIG11dGF0ZShTYW1wbGUgPSAibWZlIiwgRXhwZXJpbWVudCA9ICJTVEFVMSBub25oeWJyaWRzIikKCmZpbHRlcmVkLm5vbmh5Yi5tZmUuZGYgPC0gZHBseXI6OnJlbmFtZShmaWx0ZXJlZC5ub25oeWIubWZlLmRmLCBNRkUgPSBldmFsX21mZSkKYGBgCgpgYGB7cn0KIyBBcHBlbmQgZGF0YXNldHMgZm9yIGFsbCBleHBlcmltZW50cwpzdGF1MS5oeWIuZmlsdGVyZWRfbm9uaHliLmR0IDwtIGJpbmRfcm93cyhub2xpbmtlci5tZmUuZGYsIHBhcmlzLm1mZS5kZiwgZmlsdGVyZWQubm9uaHliLm1mZS5kZikKc3RhdTEuaHliLmZpbHRlcmVkX25vbmh5Yi5kdCA8LSBzdGF1MS5oeWIuZmlsdGVyZWRfbm9uaHliLmR0ICU+JQogIG11dGF0ZShzYW1wbGUgPSBjYXNlX3doZW4oKFNhbXBsZSA9PSAibWZlIikgfiAiU1RBVTEiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgKFNhbXBsZSA9PSAic2h1ZmZsZWRfbWZlIikgfiAiU2h1ZmZsZWQgY29udHJvbCIpKQpgYGAKCmBgYHtyfQpzdGF1MS5oeWIuZmlsdGVyZWRfbm9uaHliLmdnIDwtIGdncGxvdChzdGF1MS5oeWIuZmlsdGVyZWRfbm9uaHliLmR0LCBhZXMoeCA9IE1GRSwgbGluZXR5cGUgPSBTYW1wbGUpKSArIAogIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuOCkgKwogIHNjYWxlX2NvbG91cl9icmV3ZXIocGFsZXR0ZT0iRGFyazIiKSArCiAgc2NhbGVfbGluZXR5cGVfbWFudWFsKHZhbHVlcz1jKCAic29saWQiLCAibG9uZ2Rhc2giKSkrCiAgZmFjZXRfZ3JpZChjb2xzID0gdmFycyhFeHBlcmltZW50KSkgKwogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCksIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCkpICsKICB5bGFiKCJEZW5zaXR5IikgKwogIHhsYWIoIk1GRSAoa2NhbC9tb2wpIikgKwogIGdndGl0bGUoIjMnVVRSIGNsdXN0ZXJzIG1pbiBNRkUgLSBmaWx0ZXJlZCIpCgpzdGF1MS5oeWIuZmlsdGVyZWRfbm9uaHliLmdnCgpgYGAKYGBge3J9CiMgYWxsCnN0YXUxLmh5Yi5maWx0ZXJlZF9ub25oeWIuZHQgPC0gc3RhdTEuaHliLmZpbHRlcmVkX25vbmh5Yi5kdCAlPiUKICBkcGx5cjo6ZmlsdGVyKFNhbXBsZSAhPSAiU2h1ZmZsZWQgY29udHJvbCIpCgptZmUuZmlsdGVyZWQudmlvbGluLmdnIDwtIGdncGxvdChzdGF1MS5oeWIuZmlsdGVyZWRfbm9uaHliLmR0LCBhZXMoeCA9IEV4cGVyaW1lbnQsIHkgPSBNRkUsIGZpbGwgPSBFeHBlcmltZW50KSkgKwogIGdlb21fdmlvbGluKCkgKwogIGdlb21fYm94cGxvdCh3aWR0aD0wLjEpICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlPSJCbHVlcyIpICsKICAjc2NhbGVfY29sb3JfZ3JleSgpICsKICAjc3RhdF9zdW1tYXJ5KGZ1bi55PW1lZGlhbiwgZ2VvbT0icG9pbnQiLCBzaXplPTIsIGNvbG9yPSJncmV5IikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0icmlnaHQiLCBsZWdlbmQuZGlyZWN0aW9uPSJ2ZXJ0aWNhbCIsCiAgICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpICsKICAgIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCksIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCkpKwogIGdndGl0bGUoIjMnVVRSIGNsdXN0ZXJzIG1pbiBNRkUiKQoKbWZlLmZpbHRlcmVkLnZpb2xpbi5nZwpnZ3NhdmUoImZpbHRlcmVkX21mZV9jb21wYXJpc29uLnBkZiIsIG1mZS5maWx0ZXJlZC52aW9saW4uZ2csIGRwaSA9IDMwMCkKCmBgYAojIyBEdXBsZXggdHlwZXMKCgpgYGB7cn0KIyBObyBsaW5rZXIKCm5vbGlua2VyLmZlYXQuZGYgPC0gbm9saW5rZXJfZm9yZ2kuZHQgJT4lIHJlcGxhY2UoaXMubmEoLiksIDApICMgcmVwbHFjZSBOQSB3aXRoIHplcm8gdG8gc2ltcGxpZnkgZmlsdGVyaW5nCgpub2xpbmtlci5mZWF0LmRmIDwtIG5vbGlua2VyLmZlYXQuZGYgJT4lCiAgZ3JvdXBfYnkoaWQpICU+JQogIG11dGF0ZShzdGVtX3R5cGUgPSBjYXNlX3doZW4oIWFueShlbGVtZW50X3R5cGUgPT0gImkiKSB+ICJVbmludGVycnVwdGVkIHN0ZW0iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYW55KGVsZW1lbnRfdHlwZSA9PSAiaSIpICYgKCFhbnkoZWxlbWVudF90eXBlID09ICJpIiAmIChMX3dpZHRoID4gMSB8IFJfd2lkdGggPiAxKSkpICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKCFhbnkoZWxlbWVudF90eXBlID09ICJpIiAmIExfd2lkdGggIT0gUl93aWR0aCkpIH4gIlN0ZW0gd2l0aCBvbmx5IHN5bW1ldHJpY2FsIGJ1bGdlcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoYW55KGVsZW1lbnRfdHlwZSA9PSAiaSIpKSAmICghYW55KGVsZW1lbnRfdHlwZSA9PSAiaSIgJiAoTF93aWR0aCA+IDEgfCBSX3dpZHRoID4gMSkpKSAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChhbnkoZWxlbWVudF90eXBlID09ICJpIiAmIExfd2lkdGggIT0gUl93aWR0aCkpIH4gIlN0ZW0gd2l0aCBub24tc3ltbWV0cmljYWwgYnVsZ2VzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChhbnkoZWxlbWVudF90eXBlID09ICJpIikpICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKGFueShlbGVtZW50X3R5cGUgPT0gImkiICYgKExfd2lkdGggPiAxKSAmIChSX3dpZHRoID09IDAgfCBSX3dpZHRoID49IDEpKSkgfAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoYW55KGVsZW1lbnRfdHlwZSA9PSAiaSIgJiAoTF93aWR0aCA9IDEpICYgKFJfd2lkdGggPiAxKSkpIH4gIlN0ZW0gd2l0aCBpbnRlcm5hbCBsb29wcyIpKQoKCm5vbGlua2VyX3BpZS5nZyA8LSBkcmF3X3BpZV9jaGFydChub2xpbmtlci5mZWF0LmRmLCBzdGVtX3R5cGUpCmdnc2F2ZShwYXN0ZTAoIm5vbGlua2VyIiwgIl9zdGVtX3R5cGVzX3BpZS5wZGYiKSwgbm9saW5rZXJfcGllLmdnLCBoZWlnaHQgPSAxMSwgd2lkdGggPSA3LCBkcGkgPSAzMDApCm5vbGlua2VyX3BpZS5nZyArIGdndGl0bGUoIlNUQVUxIGh5YnJpZHMiKQoKIyBQQVJJUwoKcGFyaXMuZmVhdC5kZiA8LSBwYXJpcy5mb3JnaS5kdCAlPiUgcmVwbGFjZShpcy5uYSguKSwgMCkgIyByZXBscWNlIE5BIHdpdGggemVybyB0byBzaW1wbGlmeSBmaWx0ZXJpbmcKCnBhcmlzLmZlYXQuZGYgPC0gcGFyaXMuZmVhdC5kZiAlPiUKICBncm91cF9ieShpZCkgJT4lCiAgbXV0YXRlKHN0ZW1fdHlwZSA9IGNhc2Vfd2hlbighYW55KGVsZW1lbnRfdHlwZSA9PSAiaSIpIH4gIlVuaW50ZXJydXB0ZWQgc3RlbSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbnkoZWxlbWVudF90eXBlID09ICJpIikgJiAoIWFueShlbGVtZW50X3R5cGUgPT0gImkiICYgKExfd2lkdGggPiAxIHwgUl93aWR0aCA+IDEpKSkgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoIWFueShlbGVtZW50X3R5cGUgPT0gImkiICYgTF93aWR0aCAhPSBSX3dpZHRoKSkgfiAiU3RlbSB3aXRoIG9ubHkgc3ltbWV0cmljYWwgYnVsZ2VzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChhbnkoZWxlbWVudF90eXBlID09ICJpIikpICYgKCFhbnkoZWxlbWVudF90eXBlID09ICJpIiAmIChMX3dpZHRoID4gMSB8IFJfd2lkdGggPiAxKSkpICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKGFueShlbGVtZW50X3R5cGUgPT0gImkiICYgTF93aWR0aCAhPSBSX3dpZHRoKSkgfiAiU3RlbSB3aXRoIG5vbi1zeW1tZXRyaWNhbCBidWxnZXMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKGFueShlbGVtZW50X3R5cGUgPT0gImkiKSkgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoYW55KGVsZW1lbnRfdHlwZSA9PSAiaSIgJiAoTF93aWR0aCA+IDEpICYgKFJfd2lkdGggPT0gMCB8IFJfd2lkdGggPj0gMSkpKSB8CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChhbnkoZWxlbWVudF90eXBlID09ICJpIiAmIChMX3dpZHRoID0gMSkgJiAoUl93aWR0aCA+IDEpKSkgfiAiU3RlbSB3aXRoIGludGVybmFsIGxvb3BzIikpCgoKcGFyaXNfcGllLmdnIDwtIGRyYXdfcGllX2NoYXJ0KHBhcmlzLmZlYXQuZGYsIHN0ZW1fdHlwZSkKZ2dzYXZlKHBhc3RlMCgicGFyaXMiLCAiX3N0ZW1fdHlwZXNfcGllLnBkZiIpLCBub2xpbmtlcl9waWUuZ2csIGhlaWdodCA9IDExLCB3aWR0aCA9IDcsIGRwaSA9IDMwMCkKcGFyaXNfcGllLmdnICsgZ2d0aXRsZSgiUEFSSVMgaHlicmlkcyIpCgojIE5vbmh5YnJpZHMKCm5vcm5hc2UuZm9yZ2kuZ2cgPC0gZHJhd19waWVfY2hhcnQobm9ybmFzZS5mb3JnaS5kdCwgc3RlbV90eXBlKQpnZ3NhdmUoIm5vbl9oeWJfc3RlbV90eXBlc19waWUucGRmIiwgbm9ybmFzZS5mb3JnaS5nZywgZHBpID0gMzAwLCBoZWlnaHQgPSAxMSwgd2lkdGggPSA3KQpub3JuYXNlLmZvcmdpLmdnICsgZ2d0aXRsZSgiU1RBVTEgbm9uaHlicmlkcyIpCgoKZmlsdGVyZWQubm9uaHliLmZvcmdpLmdnIDwtIGRyYXdfcGllX2NoYXJ0KGZpbHRlcmVkLm5vbmh5Yi5mb3JnaS5kZiwgc3RlbV90eXBlKQpnZ3NhdmUoImZpbHRlcmVkX25vbl9oeWJfc3RlbV90eXBlc19waWUucGRmIiwgZmlsdGVyZWQubm9uaHliLmZvcmdpLmdnLCBkcGkgPSAzMDAsIGhlaWdodCA9IDExLCB3aWR0aCA9IDcpCmZpbHRlcmVkLm5vbmh5Yi5mb3JnaS5nZyArIGdndGl0bGUocGFzdGUwKCJTVEFVMSBub25oeWJyaWRzICIsIDgsICItIiwgbWF4LCIgYnAgbG9uZyIpKQoKYGBgCiMjIFN0ZW0gbGVuZ3RocwoKYGBge3J9CiMgcGFyaXMuZHQkdG90YWxfcGFpcmVkCm5vbGlua2VyLmZlYXQuZGYgPC0gYW5hbHlzZV9kdXBsZXhlcyhub2xpbmtlci5mZWF0LmRmKQoKcGFyaXMuZmVhdC5kZiA8LSBhbmFseXNlX2R1cGxleGVzKHBhcmlzLmZlYXQuZGYpCmBgYAoKYGBge3J9Cm5vbGlua2VyX2ZlYXR1cmVzLmRmIDwtIGdldF9pZF9mZWF0dXJlcyhub2xpbmtlci5mZWF0LmRmKQpub25oeWJfZmVhdHVyZXMuZGYgPC0gZ2V0X2lkX2ZlYXR1cmVzKG5vcm5hc2UuZm9yZ2kuZHQpCmZpbHRlcmVkX25vbmh5Yl9mZWF0dXJlcy5kZiA8LSBnZXRfaWRfZmVhdHVyZXMoZmlsdGVyZWQubm9uaHliLmZvcmdpLmRmKQpwYXJpc19mZWF0dXJlcy5kZiA8LSBnZXRfaWRfZmVhdHVyZXMocGFyaXMuZmVhdC5kZikKCmZpbHRlcmVkX25vbmh5Yl9mZWF0dXJlcy5kZiRFeHBlcmltZW50IDwtICJTVEFVMSBub25oeWJyaWRzIChmaWx0ZXJlZCkiCm5vbGlua2VyX2ZlYXR1cmVzLmRmJEV4cGVyaW1lbnQgPC0gIlNUQVUxIGh5YnJpZHMiCm5vbmh5Yl9mZWF0dXJlcy5kZiRFeHBlcmltZW50IDwtICJTVEFVMSBub25oeWJyaWRzIgpwYXJpc19mZWF0dXJlcy5kZiRFeHBlcmltZW50IDwtICJQQVJJUyIKCmFsbC5mZWF0dXJlcy5kZiA8LSBiaW5kX3Jvd3Mobm9saW5rZXJfZmVhdHVyZXMuZGYsIGZpbHRlcmVkX25vbmh5Yl9mZWF0dXJlcy5kZiwgbm9uaHliX2ZlYXR1cmVzLmRmLCBwYXJpc19mZWF0dXJlcy5kZikKYGBgCgoKYGBge3J9CmFsbC5mZWF0dXJlcy5kZiA8LSByb3dpZF90b19jb2x1bW4oYWxsLmZlYXR1cmVzLmRmKQphbGwuZmVhdHVyZXMuZGYgPC0gYWxsLmZlYXR1cmVzLmRmICU+JSAKICBnYXRoZXIoImZlYXR1cmUiLCAiY291bnRzIiwgbWF4X2R1cGxleDppbG9vcF9jb3VudHMpCmBgYAoKCiMjIER1cGxleCBmZWF0dXJlcwoKYGBge3J9CmFsbF9mZWF0dXJlcy5nZyA8LSBnZ3Bsb3QodHJhbnNmb3JtKGFsbC5mZWF0dXJlcy5kZiwgZmVhdHVyZSA9IGZhY3RvcihmZWF0dXJlLCBsZXZlbHMgPSBjKCJzdW1fcGFpcmVkIiwgIm1heF9kdXBsZXgiLCJpbG9vcF9jb3VudHMiKSkpLAogICAgICAgICAgICAgICAgICAgICAgICAgYWVzKHggPSAiIiwgeT1jb3VudHMsIGZpbGw9RXhwZXJpbWVudCkpICsKICBnZW9tX2JveHBsb3QoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCkpICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlPSJCbHVlcyIpICsKICBmYWNldF93cmFwKC4gfiBmZWF0dXJlLCBzY2FsZXMgPSAiZnJlZSIpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb249InJpZ2h0IiwgbGVnZW5kLmRpcmVjdGlvbj0idmVydGljYWwiKQoKYWxsX2ZlYXR1cmVzLmdnCiMgZ2dzYXZlKCJmZWF0dXJlX2NvbXBhcmlzb25fYm94cGxvdC5wZGYiLCBhbGxfZmVhdHVyZXMuZ2csIGRwaSA9IDMwMCkKYGBgCiMgTnVjbGVvdGlkZSBmcmVxdWVuY3kgYW5hbHlzaXMKCmBgYHtyfQpwYXJpcy5kdCA8LSBwYXJpcy5kdCAlPiUKICBkcGx5cjo6c2VsZWN0KC1jbHVzdGVyX25hbWUpCmBgYAoKYGBge3J9Cm5vbGlua2VyX251Y19mcmVxLmRmCgp4IDwtIG5vbGlua2VyX251Y19mcmVxLmRmICU+JSBkcGx5cjo6c2VsZWN0KGlkLCBBX2ZyZXFfbWF4X3B1cl9hcm0sIEdfZnJlcV9tYXhfcHVyX2FybSwgQ19mcmVxX21heF9wdXJfYXJtLCBVX2ZyZXFfbWF4X3B1cl9hcm0sQV9mcmVxX21pbl9wdXJfYXJtLCBHX2ZyZXFfbWluX3B1cl9hcm0sIENfZnJlcV9taW5fcHVyX2FybSwgVV9mcmVxX21pbl9wdXJfYXJtKQogICAgZHBseXI6OmRpc3RpbmN0KCkgJT4lCiAgICBkcGx5cjo6YXJyYW5nZShkZXNjKEdfZnJlcV9tYXhfcHVyX2FybSkpCmBgYAoKYGBge3J9Cm5vbGlua2VyX251Y19mcmVxLmRmIDwtIGdldF9udWNsZW90aWRlX2ZyZXF1ZW5jeShub2xpbmtlci5kdCkKbm9saW5rZXJfbnVjX2ZyZXEuZ2cgPC0gcGxvdF9udWNsZW90aWRlX2ZyZXF1ZW5jeShub2xpbmtlcl9udWNfZnJlcS5kZikKCnBhcmlzX251Y19mcmVxLmRmIDwtIGdldF9udWNsZW90aWRlX2ZyZXF1ZW5jeShwYXJpcy5kdCkKcGFyaXNfbnVjX2ZyZXEuZ2cgPC0gcGxvdF9udWNsZW90aWRlX2ZyZXF1ZW5jeShwYXJpc19udWNfZnJlcS5kZikKCm5vbm5oeWJfbnVjX2ZyZXEuZ2cgPC0gcGxvdF9udWNsZW90aWRlX2ZyZXF1ZW5jeShmaWx0ZXJlZC5ub25oeWIuZm9yZ2kuZGYpCgoKbm9saW5rZXJfbnVjX2ZyZXEuZ2cgKyBnZ3RpdGxlKCJTVEFVMSIpCm5vbm5oeWJfbnVjX2ZyZXEuZ2cgKyBnZ3RpdGxlKCJTVEFVMSBub25oeWJyaWRzIikKcGFyaXNfbnVjX2ZyZXEuZ2cgKyBnZ3RpdGxlKCJQQVJJUyIpCmBgYAoKCiMgQ2x1c3RlciBhbmFseXNlcwoKVGhlc2UgYW5hbHlzZXMgd2VyZSBwZXJmb3JtZWQgb24gdGhlIGNsdXN0ZXJlZCBoeWJyaWRzIHByb2R1Y2VkIHdpdGggdGhlIFRvc2NhIHBpcGVsaW5lLgoKIyMjIFNUQVUxIGh5YnJpZHMgZGF0YTogR2V0IGdlbm9taWMgY29vcmRpbmF0ZXMgYW5kIGFubm90YXRlIGh5YnJpZHMKCmBgYHtyLCB3YXJuaW5nID0gRkFMU0V9CgpoeWJyaWRzLmR0IDwtIGh5YnJpZHMuZHRbZ3JlcCgiXnJSTkEiLCBMX3NlcW5hbWVzLCBpbnZlcnQgPSBUUlVFKV0gIyBSZW1vdmUgclJOQQpoeWJyaWRzLmR0IDwtIGh5YnJpZHMuZHRbZ3JlcCgiTXQiLCBMX3NlcW5hbWVzLCBpbnZlcnQgPSBUUlVFKV0KCiMgUmVmb3JtYXQgbmFtZXMgb2YgaHlicmlkcyBzZXFuYW1lcyBzbyB0aGV5IG1hdGNoIHRoZSBhbm5vdGF0aW9uIGd0ZgpoeWJyaWRzLmR0WywgYygiTF9zZXFuYW1lcyIpIDo9IHRzdHJzcGxpdChMX3NlcW5hbWVzLCAiOjoiLCBmaXhlZD1UUlVFKVsxXV0KaHlicmlkcy5kdFssIGMoIlJfc2VxbmFtZXMiKSA6PSB0c3Ryc3BsaXQoTF9zZXFuYW1lcywgIjo6IiwgZml4ZWQ9VFJVRSlbMV1dCgojIENvbnZlcnQgdG8gZ2Vub21pYyBjb29yZGluYXRlcwpoeWJyaWRzLmR0IDwtIGNvbnZlcnRfY29vcmRpbmF0ZXMoaHlicmlkcy5kdCwgZ2VuZXMuZ3IpCmZ3cml0ZShoeWJyaWRzLmR0LCAiY2x1c3RlcnMuZ2MudHN2Lmd6Iiwgc2VwID0gIlx0IikKCiMgQW5ub3RhdGUgaHlicmlkcwpoeWJyaWRzLmR0IDwtIGFubm90YXRlX2h5YnJpZHMoaHlicmlkcy5kdCwgcmVnaW9ucy5ncikKZndyaXRlKGh5YnJpZHMuZHQsICJhbm5vdGF0ZWRfY2x1c3RlcnMuZ2MudHN2Lmd6Iiwgc2VwID0gIlx0IikKCmh5YnJpZHMuZHQkRXhwZXJpbWVudCA8LSAiU1RBVTEgLSBObyBsaW5rZXIiCmBgYAoKIyMjIFBBUklTIGRhdGE6IEdldCBnZW5vbWljIGNvb3JkaW5hdGVzIGFuZCBhbm5vdGF0ZSBoeWJyaWRzCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFfQoKcGFyaXNfY2x1c3RlcnMuZHQgPC0gcGFyaXNfY2x1c3RlcnMuZHRbZ3JlcCgiXnJSTkEiLCBMX3NlcW5hbWVzLCBpbnZlcnQgPSBUUlVFKV0gIyBSZW1vdmUgclJOQQpwYXJpc19jbHVzdGVycy5kdCA8LSBwYXJpc19jbHVzdGVycy5kdFtncmVwKCJNdCIsIExfc2VxbmFtZXMsIGludmVydCA9IFRSVUUpXQoKcGFyaXNfY2x1c3RlcnMuZHRbLCBjKCJMX3NlcW5hbWVzIikgOj0gdHN0cnNwbGl0KExfc2VxbmFtZXMsICI6OiIsIGZpeGVkPVRSVUUpWzFdXQpwYXJpc19jbHVzdGVycy5kdFssIGMoIlJfc2VxbmFtZXMiKSA6PSB0c3Ryc3BsaXQoTF9zZXFuYW1lcywgIjo6IiwgZml4ZWQ9VFJVRSlbMV1dCgojIENvbnZlcnQgdG8gZ2Vub21pYyBjb29yZGluYXRlcwpwYXJpc19jbHVzdGVycy5kdCA8LSBjb252ZXJ0X2Nvb3JkaW5hdGVzKHBhcmlzX2NsdXN0ZXJzLmR0LCBnZW5lcy5ncikKZndyaXRlKHBhcmlzX2NsdXN0ZXJzLmR0LCAicGFyaXNfY2x1c3RlcnMuZ2MudHN2Lmd6Iiwgc2VwID0gIlx0IikKCiMgQW5ub3RhdGUgaHlicmlkcwpwYXJpc19jbHVzdGVycy5kdCA8LSBhbm5vdGF0ZV9oeWJyaWRzKHBhcmlzX2NsdXN0ZXJzLmR0LCByZWdpb25zLmdyKQpmd3JpdGUocGFyaXNfY2x1c3RlcnMuZHQsICJwYXJpc19hbm5vdGF0ZWRfY2x1c3RlcnMuZ2MudHN2Lmd6Iiwgc2VwID0gIlx0IikKcGFyaXNfY2x1c3RlcnMuZHQkRXhwZXJpbWVudCA8LSAiUEFSSVMiCmBgYAoKYGBge3J9CiMgYWRkIGNvbCBjbHVzdGVyX25hbWUgYmVjYXVzZSBQQVJJUyBkYXRhIGRvZXMgbm90IGhhdmUgdW5pcXVlIGNsdXN0ZXIgaWRlbnRpZmllcnMKcGFyaXNfY2x1c3RlcnMuZHQgPC0gcGFyaXNfY2x1c3RlcnMuZHQgJT4lIHVuaXRlKCJjbHVzdGVyX25hbWUiLCBjKG5hbWUsIExfc2VxbmFtZXMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmEucm0gPSBUUlVFLCByZW1vdmUgPSBGQUxTRSwgc2VwPSJfIikKICAKICAKICAKaHlicmlkcy5kdCA8LSBoeWJyaWRzLmR0ICU+JSB1bml0ZSgiY2x1c3Rlcl9uYW1lIiwgYyhuYW1lLCBMX3NlcW5hbWVzKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hLnJtID0gVFJVRSwgcmVtb3ZlID0gRkFMU0UsIHNlcD0iXyIpCmBgYAoKCiMjIyMgTWVyZ2UgZXhwZXJpbWVudHMKCmBgYHtyfQphbGxfZXhwLmRmIDwtIGFzLmRhdGEuZnJhbWUocmJpbmQoaHlicmlkcy5kdCwgcGFyaXNfY2x1c3RlcnMuZHQpKQojIGludHJhbW9sZWN1bGFyIG9ubHkKYWxsX2V4cC5kZiA8LSBhbGxfZXhwLmRmICU+JSBkcGx5cjo6ZmlsdGVyKExfZ2VuZV9uYW1lID09IFJfZ2VuZV9uYW1lKQpgYGAKCgojIyBUcmFuc2NyaXB0IGJpb3R5cGVzCmBgYHtyfQpiaW90eXBlX2NvdW50cy5kZiA8LSBhbGxfZXhwLmRmICU+JQogIGRwbHlyOjpzZWxlY3QoY2x1c3Rlcl9uYW1lLCBFeHBlcmltZW50LCBMX2Jpb3R5cGUsIFJfYmlvdHlwZSkgJT4lCiAgcGl2b3RfbG9uZ2VyKGMoTF9iaW90eXBlLCBSX2Jpb3R5cGUpLCBuYW1lc190byA9ICJhcm0iLCB2YWx1ZXNfdG8gPSAiYmlvdHlwZSIpCgpiaW90eXBlX2NvdW50cy5kZiA8LSBiaW90eXBlX2NvdW50cy5kZiAlPiUKICBncm91cF9ieShFeHBlcmltZW50LCBiaW90eXBlKSAlPiUKICBtdXRhdGUoYmlvdHlwZSA9IGNhc2Vfd2hlbihzdHJfZGV0ZWN0KGJpb3R5cGUsICJsbmNSTkEiKSB+ICJsbmNSTkEiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgfiBiaW90eXBlKSkgJT4lCiAgc3VtbWFyaXNlKGNvdW50cyA9IG4oKSkgJT4lCiAgYXJyYW5nZShkZXNjKGJpb3R5cGUpKSAlPiUKICBtdXRhdGUocGVyY2VudGFnZSA9IGNvdW50cyoxMDAgLyBzdW0oY291bnRzKSkgJT4lCiAgdW5ncm91cCgpCgpiaW90eXBlX2NvdW50cy5kZiA8LSBiaW90eXBlX2NvdW50cy5kZiAlPiUKICBtdXRhdGUoUk5BX2Jpb3R5cGUgPSBjYXNlX3doZW4oKHBlcmNlbnRhZ2UgPCAwLjA1KSB+ICJPdGhlciIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChwZXJjZW50YWdlID49IDAuMDUpIH4gYmlvdHlwZSkpCgpiaW90eXBlX2NvdW50cy5kZiA8LSBiaW90eXBlX2NvdW50cy5kZiAlPiUKICBncm91cF9ieShFeHBlcmltZW50LCBSTkFfYmlvdHlwZSkgJT4lCiAgbXV0YXRlKGJpb3R5cGVfcGVyY2VudGFnZSA9IHN1bShwZXJjZW50YWdlKSkgJT4lCiAgdW5ncm91cCgpCgpgYGAKCmBgYHtyIGVjaG89RkFMU0V9CmJpb3R5cGVfY291bnRzLmRmCmBgYAoKYGBge3J9CiMgU2ltcGxlIGJhci1jaGFydAoKZ2dwbG90KGRpc3RpbmN0KGJpb3R5cGVfY291bnRzLmRmLCBhY3Jvc3MoYyhFeHBlcmltZW50LCBSTkFfYmlvdHlwZSwgYmlvdHlwZV9wZXJjZW50YWdlKSkpLAogICAgICAgYWVzKFJOQV9iaW90eXBlLCBiaW90eXBlX3BlcmNlbnRhZ2UpKSArIAogIGdlb21fYmFyKHBvc2l0aW9uPSJkb2RnZSIsIHN0YXQgPSAiaWRlbnRpdHkiLCBhZXMoZmlsbCA9IEV4cGVyaW1lbnQpLCBhbHBoYSA9IDAuOCkgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGU9IkRhcmsyIikgKwogICNzY2FsZV9maWxsX3BhbGV0dGVlcl9kKCJyY2FydG9jb2xvcjo6RWFydGgiLCBkaXJlY3Rpb24gPSAtMSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0icmlnaHQiLCBsZWdlbmQuZGlyZWN0aW9uPSJ2ZXJ0aWNhbCIsCiAgICAgICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHZqdXN0ID0gMC41LCBoanVzdD0xKSkgKwogIHlsYWIoIlBlcmNlbnRhZ2UiKSArCiAgeGxhYigiUk5BIGJpb3R5cGUiKQpgYGAKCiMjIG1STkEgdHJhbnNjcmlwdCByZWdpb25zCgpgYGB7cn0KYWxsX2V4cF9tUk5BLmRmIDwtIGFsbF9leHAuZGYgJT4lIGRwbHlyOjpmaWx0ZXIoUl9iaW90eXBlICA9PSAibVJOQSIgJiBMX2Jpb3R5cGUgPT0gIm1STkEiKQoKYWxsX2V4cF9tUk5BLmRmIDwtIGFsbF9leHBfbVJOQS5kZiAlPiUKICBtdXRhdGUoaHlicmlkX3R5cGUgPSBjYXNlX3doZW4oKExfcmVnaW9uID09ICJVVFIzIiAmIFJfcmVnaW9uID09ICJVVFIzIikgfiAiMydVVFItMydVVFIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoTF9yZWdpb24gPT0gIkNEUyIgJiBSX3JlZ2lvbiA9PSAiQ0RTIikgfiAiQ0RTLUNEUyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChMX3JlZ2lvbiA9PSAiVVRSMyIgJiBSX3JlZ2lvbiA9PSAiQ0RTIikgfiAiMydVVFItQ0RTIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKExfcmVnaW9uID09ICJDRFMiICYgUl9yZWdpb24gPT0gIlVUUjMiKSB+ICIzJ1VUUi1DRFMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoTF9yZWdpb24gPT0gIlVUUjUiICYgUl9yZWdpb24gPT0gIlVUUjUiKSB+ICI1J1VUUi01J1VUUiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChMX3JlZ2lvbiA9PSAiVVRSMyIgJiBSX3JlZ2lvbiA9PSAiVVRSNSIpIH4gIjMnVVRSLTUnVVRSIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKExfcmVnaW9uID09ICJVVFI1IiAmIFJfcmVnaW9uID09ICJVVFIzIikgfiAiMydVVFItNSdVVFIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoTF9yZWdpb24gPT0gIlVUUjUiICYgUl9yZWdpb24gPT0gIkNEUyIpIH4gIjUnVVRSLUNEUyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChMX3JlZ2lvbiA9PSAiQ0RTIiAmIFJfcmVnaW9uID09ICJVVFI1IikgfiAiNSdVVFItQ0RTIikpCgoKcGxvdF9zdGFja2VkX2JhcmNoYXJ0KGFsbF9leHBfbVJOQS5kZiwgaHlicmlkX3R5cGUsIEV4cGVyaW1lbnQpCgpgYGAKCgojIyAzJ1VUUiBoeWJyaWRzCgpgYGB7cn0KIyBHZXQgaHlicmlkIHNwYW5zOyBHZXQgaHlicmlkIGFybSB3aWR0aHMKCnRocmVldXRyLmRmIDwtIGFsbF9leHBfbVJOQS5kZiAlPiUKICBkcGx5cjo6ZmlsdGVyKExfcmVnaW9uID09ICJVVFIzIiAmIFJfcmVnaW9uID09ICJVVFIzIikKCnRocmVldXRyLmRmIDwtIHRocmVldXRyLmRmICU+JQogIHJvd3dpc2UoKSAlPiUKICBtdXRhdGUoaHlicmlkX3NwYW4gPSBSX3N0YXJ0IC0gTF9lbmQgKyAxLCAKICAgICAgICAgTF9hcm1fd2lkdGggPSBMX2VuZCAtIExfc3RhcnQgKyAxLAogICAgICAgICBSX2FybV93aWR0aCA9IFJfZW5kIC0gUl9zdGFydCArIDEpCmBgYAoKIyMjIEh5YnJpZCBzcGFuCgpgYGB7cn0KZ2dwbG90KHRocmVldXRyLmRmLCBhZXMoeCA9IGh5YnJpZF9zcGFuLCBjb2xvciA9IEV4cGVyaW1lbnQpKSArIAogIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuOCkgKwogIHNjYWxlX2NvbG91cl9icmV3ZXIocGFsZXR0ZT0iRGFyazIiKSArCiAgc2NhbGVfeF9sb2cxMCgpICsgYW5ub3RhdGlvbl9sb2d0aWNrcygpICsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpKSArCiAgeWxhYigiRGVuc2l0eSIpICsKICB4bGFiKCJIeWJyaWQgc3BhbiIpCmBgYAoKIyMjIEFybSB3aWR0aAoKYGBge3J9CiMgY29udmVydCB0byBsb25nIGZvcm1hdAp0aHJlZXV0cl9sb25nLmRmIDwtIHRocmVldXRyLmRmICU+JQogIHBpdm90X2xvbmdlcihjKFJfYXJtX3dpZHRoLCBMX2FybV93aWR0aCksIG5hbWVzX3RvID0gImFybSIsIHZhbHVlc190byA9ICJhcm1fd2lkdGgiKQoKZ2dwbG90KHRocmVldXRyX2xvbmcuZGYsIGFlcyh4ID0gYXJtLCB5ID0gYXJtX3dpZHRoLCBmaWxsID0gRXhwZXJpbWVudCkpICsKICBnZW9tX2JveHBsb3QoYWxwaGEgPSAwLjcpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSkgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGU9IkRhcmsyIikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0icmlnaHQiLCBsZWdlbmQuZGlyZWN0aW9uPSJ2ZXJ0aWNhbCIpICsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpKSArCiAgeWxhYigiSHlicmlkIGFybSB3aWR0aCIpICsKICB4bGFiKCJBcm0iKQpgYGAK